Skip to Content
OpenVAPTAlpha
Pre-release. Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. Free for your own use, 100% at your own risk. This website collects no data. Read MoreRead Less
Pre-release. OpenVAPT has no Stable release yet. Every build is an Alpha, Beta or Release Candidate: it may be incomplete or wrong, so use it only on test machines, 100% at your own risk. See Release Channels
Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. These are AI systems, not people or registered engineers. Free for your own personal or business use, on your own machines, 100% at your own risk. Not for sale. Not for providing services. This website collects no data. Read the Acknowledgements
Pre-release. OpenVAPT has no Stable release yet. Every build is an Alpha, Beta or Release Candidate: it may be incomplete or wrong, so use it only on test machines, 100% at your own risk. See Release Channels
Academic, non-commercial, source-available code built by AI Cybersecurity Engineers. These are AI systems, not people or registered engineers. Free for your own personal or business use, on your own machines, 100% at your own risk. Not for sale. Not for providing services. This website collects no data. Read the Acknowledgements
MediumWEB-CK-001Web Application SecurityWeb Apps

Cookies Missing Secure, HttpOnly or SameSite

Checks the security flags on cookies the home page sets.

Why It Matters
Without Secure, cookies leak over plain HTTP. Without HttpOnly, any XSS bug can steal sessions. Without SameSite, cross-site request forgery is easier.
How It Detects
Inspects every Set-Cookie header on each site's home page for the Secure (HTTPS sites), HttpOnly and SameSite attributes. Cookies being deleted (expired or Max-Age 0) are ignored, and anti-CSRF cookies such as XSRF-TOKEN are not required to be HttpOnly because scripts must read them. Cookies set on redirect responses are evaluated too, because browsers store them. Cookie values are never recorded.
How to Fix
PHP: session.cookie_secure=1, session.cookie_httponly=1, session.cookie_samesite=Lax in php.ini . Frameworks have equivalent settings (e.g. Laravel config/session.php). Use HttpOnly for every cookie JavaScript does not need to read.
How to Verify
curl -sI https://<site>/ | grep -i set-cookie
CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N
Control Category
Web Application Security: CIS Controls v8 16; NIST CSF 2.0 PR.PS, PR.DS
Fix Effort
Medium
CIS Controls v8 Safeguard
16.7
NIST CSF 2.0 Subcategory
PR.PS-01
OWASP WSTG v4.2
WSTG-SESS-02
CWE
CWE-614, CWE-1004, CWE-1275
OWASP Top 10
A05:2021, A07:2021
OWASP ASVS 4.0.3
3.4.1, 3.4.2, 3.4.3
NIST SP 800-53 Rev. 5
SC-8, SC-23
References
OWASP Session Management Cheat Sheet
RFC 6265, HTTP State Management Mechanism
Learn the theory behind this check: P5 Web Security Headers and Cookies.

Source Code

The complete file that defines this check. Run it with openvapt scan --only WEB-CK-001, or read it offline with openvapt explain WEB-CK-001.

internal/checks/web/transport.go · Lines 1-127View on GitHub at This Commit
1package web
2
3import (
4 "context"
5 "errors"
6 "io/fs"
7 "net/http"
8 "strings"
9 "syscall"
10 "time"
11
12 "github.com/openvapt/openvapt/internal/check"
13 "github.com/openvapt/openvapt/internal/sys"
14)
15
16func init() {
17 check.Register(&check.Check{
18 ID: "WEB-TLS-001",
19 Title: "Site Available Over Plain HTTP Without Redirect",
20 Domain: check.DomainWeb,
21 Severity: check.Medium,
22 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
23 Summary: "Checks that every site redirects http:// requests to https://.",
24 Rationale: "Pages served over plain HTTP can be read and changed by anyone on the network path, including injected malware and stolen session cookies.",
25 Detection: "Requests http://<site>/ through 127.0.0.1 without following redirects. A 301/302/307/308 to an https:// URL passes, as does a refused connection (nothing listens on port 80). Any other response fails. A timeout or other error is Incomplete, because a slow or half-open port 80 proves nothing.",
26 Fix: "Apache: in the port 80 vhost add Redirect permanent / https://<site>/ . nginx: return 301 https://$host$request_uri; . Get a free certificate with certbot if HTTPS is not set up yet.",
27 Verify: "curl -sI http://<site>/ should show a 301 with Location: https://...",
28 Refs: []check.Ref{{Title: "OWASP Transport Layer Security Cheat Sheet", URL: "https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html"}},
29 Standards: check.Standards{CWE: []string{"CWE-319"}, OWASP: []string{"A02:2021"}, ASVS: []string{"9.1.1"}, WSTG: []string{"WSTG-CRYP-01"}, NIST: []string{"SC-8"}},
30 Run: func(ctx context.Context, s sys.System) []check.Result {
31 return ForEachSite(ctx, s, func(st Site) check.Result {
32 r, err := s.HTTP(ctx, "http", st.Name, "/")
33 if err != nil {
34 if notServed(err) {
35 return check.PassR(st.Name, "Plain HTTP is not served (connection refused on port 80)")
36 }
37 return check.IncompleteR(st.Name, "Plain HTTP could not be assessed: the request to port 80 failed without a refusal (timeout or other error), so it is unknown whether the site is served over HTTP", err.Error())
38 }
39 loc := r.Header.Get("Location")
40 if r.Status >= 300 && r.Status < 400 && strings.HasPrefix(strings.ToLower(loc), "https://") {
41 return check.PassR(st.Name, "HTTP redirects to HTTPS", loc)
42 }
43 return check.FailR(st.Name, "Site content is served over plain HTTP", "HTTP status "+itoa(r.Status))
44 })
45 },
46 })
47
48 check.Register(&check.Check{
49 ID: "WEB-CK-001",
50 Title: "Cookies Missing Secure, HttpOnly or SameSite",
51 Domain: check.DomainWeb,
52 Severity: check.Medium,
53 CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
54 Summary: "Checks the security flags on cookies the home page sets.",
55 Rationale: "Without Secure, cookies leak over plain HTTP. Without HttpOnly, any XSS bug can steal sessions. Without SameSite, cross-site request forgery is easier.",
56 Detection: "Inspects every Set-Cookie header on each site's home page for the Secure (HTTPS sites), HttpOnly and SameSite attributes. Cookies being deleted (expired or Max-Age 0) are ignored, and anti-CSRF cookies such as XSRF-TOKEN are not required to be HttpOnly because scripts must read them. Cookies set on redirect responses are evaluated too, because browsers store them. Cookie values are never recorded.",
57 Fix: "PHP: session.cookie_secure=1, session.cookie_httponly=1, session.cookie_samesite=Lax in php.ini . Frameworks have equivalent settings (e.g. Laravel config/session.php). Use HttpOnly for every cookie JavaScript does not need to read.",
58 Verify: "curl -sI https://<site>/ | grep -i set-cookie",
59 Refs: []check.Ref{{Title: "OWASP Session Management Cheat Sheet", URL: "https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html"}, {Title: "RFC 6265, HTTP State Management Mechanism", URL: "https://www.rfc-editor.org/rfc/rfc6265.html"}},
60 Standards: check.Standards{CWE: []string{"CWE-614", "CWE-1004", "CWE-1275"}, OWASP: []string{"A05:2021", "A07:2021"}, ASVS: []string{"3.4.1", "3.4.2", "3.4.3"}, WSTG: []string{"WSTG-SESS-02"}, NIST: []string{"SC-8", "SC-23"}},
61 Run: func(ctx context.Context, s sys.System) []check.Result {
62 return ForEachSite(ctx, s, func(st Site) check.Result {
63 // Cookies set on a redirect are stored by the browser like
64 // any other, so a 3xx home page is evaluated too.
65 if len(st.Home.Cookies) == 0 {
66 return check.PassR(st.Name, "Home page sets no cookies")
67 }
68 var bad []string
69 for _, c := range st.Home.Cookies {
70 if deletionCookie(c) {
71 continue // a cookie being cleared carries no value to protect
72 }
73 var miss []string
74 if st.HTTPS && !c.Secure {
75 miss = append(miss, "Secure")
76 }
77 if !c.HttpOnly && !csrfCookie(c.Name) {
78 miss = append(miss, "HttpOnly")
79 }
80 if c.SameSite == 0 {
81 miss = append(miss, "SameSite")
82 }
83 if len(miss) > 0 {
84 bad = append(bad, c.Name+": missing "+strings.Join(miss, ", "))
85 }
86 }
87 if len(bad) > 0 {
88 return check.FailR(st.Name, "Cookies are missing security flags", bad...)
89 }
90 return check.PassR(st.Name, "All cookies carry Secure, HttpOnly and SameSite")
91 })
92 },
93 })
94}
95
96// notServed reports an error meaning nothing listens on the port: the kernel
97// refused the connection, or a simulated system has no response for it.
98func notServed(err error) bool {
99 return errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, fs.ErrNotExist)
100}
101
102func itoa(n int) string {
103 if n == 0 {
104 return "0"
105 }
106 var b []byte
107 for n > 0 {
108 b = append([]byte{byte('0' + n%10)}, b...)
109 n /= 10
110 }
111 return string(b)
112}
113
114// deletionCookie reports a Set-Cookie that clears a cookie: Max-Age 0 or a
115// negative value (net/http reports both as MaxAge < 0), or an expiry date in
116// the past.
117func deletionCookie(c *http.Cookie) bool {
118 return c.MaxAge < 0 || !c.Expires.IsZero() && c.Expires.Before(time.Now())
119}
120
121// csrfCookie reports an anti-CSRF token cookie (for example XSRF-TOKEN or
122// csrftoken). The double-submit pattern needs JavaScript to read it, so
123// HttpOnly is not expected.
124func csrfCookie(name string) bool {
125 n := strings.ToLower(name)
126 return strings.Contains(n, "xsrf") || strings.Contains(n, "csrf")
127}

Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.

Cite This Page

If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.

APA 7
OpenVAPT. (2026). WEB-CK-001 Cookies Missing Secure, HttpOnly or SameSite (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/WEB-CK-001.html

In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.

BibTeX
@misc{openvapt_web_ck_001,
  author       = {{OpenVAPT}},
  title        = {{WEB-CK-001 Cookies Missing Secure, HttpOnly or SameSite}},
  year         = {2026},
  howpublished = {\url{https://openvapt.com/checks/WEB-CK-001.html}},
  note         = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}

Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.

Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.