feat: Add support for sameSite field (#628)

Recent browsers (and HTTP) added "sameSite" setting to limit cross site attacks.
Add support for exporting this property from Firefox and Chrome and to export to csv and cookie-editor format.
Also extract new "session" and "hostOnly" properties for cookie-editor format.
Tested on Firefox, Chrome and Chromium.

Signed-off-by: Frediano Ziglio <freddy77@gmail.com>
This commit is contained in:
freddy77
2026-08-07 10:14:40 +08:00
committed by GitHub
parent df20f73b7a
commit 18d841f68f
6 changed files with 52 additions and 9 deletions
+15 -2
View File
@@ -14,7 +14,7 @@ import (
const (
defaultCookieQuery = `SELECT name, encrypted_value, host_key, path,
creation_utc, expires_utc, is_secure, is_httponly,
has_expires, is_persistent FROM cookies`
has_expires, is_persistent, samesite FROM cookies`
countCookieQuery = `SELECT COUNT(*) FROM cookies`
)
@@ -27,15 +27,27 @@ func extractCookies(masterKeys masterkey.MasterKeys, path string) ([]types.Cooki
hasExpire, isPersistent int
createdAt, expireAt int64
encryptedValue []byte
sameSite int
)
if err := rows.Scan(&name, &encryptedValue, &host, &cookiePath,
&createdAt, &expireAt, &isSecure, &isHTTPOnly,
&hasExpire, &isPersistent); err != nil {
&hasExpire, &isPersistent, &sameSite); err != nil {
return types.CookieEntry{}, err
}
value, _ := decryptValue(masterKeys, encryptedValue)
value = stripCookieHash(value, host)
sameSiteStr := "unspecified"
switch sameSite {
case 0:
sameSiteStr = "none"
case 1:
sameSiteStr = "lax"
case 2:
sameSiteStr = "strict"
case -1:
// not specified by Set-Cookie
}
return types.CookieEntry{
Name: name,
Host: host,
@@ -47,6 +59,7 @@ func extractCookies(masterKeys masterkey.MasterKeys, path string) ([]types.Cooki
IsPersistent: isPersistent != 0,
ExpireAt: timeEpoch(expireAt),
CreatedAt: timeEpoch(createdAt),
SameSite: sameSiteStr,
}, nil
})
if err != nil {
+15 -2
View File
@@ -10,7 +10,7 @@ import (
const (
firefoxCookieQuery = `SELECT name, value, host, path,
creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies`
creationTime, expiry, isSecure, isHttpOnly, sameSite FROM moz_cookies`
firefoxCountCookieQuery = `SELECT COUNT(*) FROM moz_cookies`
)
@@ -21,12 +21,24 @@ func extractCookies(path string) ([]types.CookieEntry, error) {
name, value, host, cookiePath string
isSecure, isHTTPOnly int
createdAt, expiry int64
sameSite int
)
if err := rows.Scan(&name, &value, &host, &cookiePath,
&createdAt, &expiry, &isSecure, &isHTTPOnly); err != nil {
&createdAt, &expiry, &isSecure, &isHTTPOnly, &sameSite); err != nil {
return types.CookieEntry{}, err
}
hasExpire := expiry > 0
sameSiteStr := "unspecified"
switch sameSite {
case 0:
sameSiteStr = "none"
case 1:
sameSiteStr = "lax"
case 2:
sameSiteStr = "strict"
case 256:
// not specified by Set-Cookie
}
return types.CookieEntry{
Name: name,
Host: host,
@@ -38,6 +50,7 @@ func extractCookies(path string) ([]types.CookieEntry, error) {
IsPersistent: hasExpire,
ExpireAt: firefoxSeconds(expiry),
CreatedAt: firefoxMicros(createdAt),
SameSite: sameSiteStr,
}, nil
})
if err != nil {
+17 -1
View File
@@ -3,6 +3,7 @@ package output
import (
"encoding/json"
"io"
"strings"
"github.com/moond4rk/hackbrowserdata/types"
)
@@ -15,6 +16,8 @@ type cookieEditorFormatter struct {
func (f *cookieEditorFormatter) ext() string { return "json" }
var sameSiteNone = "no_restriction"
func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
if len(rows) == 0 {
return nil
@@ -32,6 +35,13 @@ func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
if !c.ExpireAt.IsZero() {
expDate = float64(c.ExpireAt.Unix())
}
sameSite := &c.SameSite
switch c.SameSite {
case "none":
sameSite = &sameSiteNone
case "", "unspecified":
sameSite = nil
}
entries = append(entries, cookieEditorEntry{
Domain: c.Host,
ExpirationDate: expDate,
@@ -40,6 +50,9 @@ func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
Path: c.Path,
Secure: c.IsSecure,
Value: c.Value,
SameSite: sameSite,
Session: expDate == 0.,
HostOnly: !strings.HasPrefix(c.Host, "."),
})
}
@@ -52,10 +65,13 @@ func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
// cookieEditorEntry matches the CookieEditor browser extension's import format.
type cookieEditorEntry struct {
Domain string `json:"domain"`
ExpirationDate float64 `json:"expirationDate"`
ExpirationDate float64 `json:"expirationDate,omitempty"`
HTTPOnly bool `json:"httpOnly"`
Name string `json:"name"`
Path string `json:"path"`
Secure bool `json:"secure"`
Value string `json:"value"`
SameSite *string `json:"sameSite"`
Session bool `json:"session"`
HostOnly bool `json:"hostOnly"`
}
+2 -2
View File
@@ -106,14 +106,14 @@ func TestWrite_CSV_Cookie(t *testing.T) {
assert.Equal(t,
[]string{
"browser", "profile", "host", "path", "name", "value",
"is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at",
"is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at", "same_site",
},
records[0],
)
assert.Equal(t,
[]string{
"Chrome", "Default", ".example.com", "/", "session", "abc123",
"true", "true", "true", "true", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z",
"true", "true", "true", "true", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z", "",
},
records[1],
)
+2 -2
View File
@@ -54,7 +54,7 @@ func TestStructCSVHeader(t *testing.T) {
expect []string
}{
{"LoginEntry", types.LoginEntry{}, []string{"url", "username", "password", "created_at"}},
{"CookieEntry", types.CookieEntry{}, []string{"host", "path", "name", "value", "is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at"}},
{"CookieEntry", types.CookieEntry{}, []string{"host", "path", "name", "value", "is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at", "same_site"}},
{"BookmarkEntry", types.BookmarkEntry{}, []string{"id", "name", "type", "url", "folder", "created_at"}},
{"HistoryEntry", types.HistoryEntry{}, []string{"url", "title", "visit_count", "last_visit"}},
{"DownloadEntry", types.DownloadEntry{}, []string{"url", "target_path", "mime_type", "total_bytes", "start_time", "end_time"}},
@@ -87,7 +87,7 @@ func TestStructCSVRow(t *testing.T) {
IsSecure: true, IsHTTPOnly: true, HasExpire: true, IsPersistent: false,
ExpireAt: refTime, CreatedAt: refTime,
},
[]string{".example.com", "/", "session", "abc", "true", "true", "true", "false", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z"},
[]string{".example.com", "/", "session", "abc", "true", "true", "true", "false", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z", ""},
},
{
"HistoryEntry_int",
+1
View File
@@ -22,6 +22,7 @@ type CookieEntry struct {
IsPersistent bool `json:"is_persistent" csv:"is_persistent"`
ExpireAt time.Time `json:"expire_at" csv:"expire_at"`
CreatedAt time.Time `json:"created_at" csv:"created_at"`
SameSite string `json:"same_site" csv:"same_site"`
}
// BookmarkEntry represents a single browser bookmark.