fix(security): replace nookies with patched cookie utility

This commit is contained in:
2026-11-04 06:54:36 +00:00
parent cd0c690454
commit 3f91a3e095
25 changed files with 487 additions and 56 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
import useTranslation from "next-translate/useTranslation";
import { parseCookies, setCookie } from "nookies";
import { parseCookies, setCookie } from "../lib/cookies";
import { useEffect, useRef, useState } from "react";
const COOKIE_NAME = "pedwAiPolicyNoticeHidden";
@@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { updateAccount } from "../../actions/services/accountService";
import { setCookie } from "nookies";
import { setCookie } from "../../lib/cookies";
const PersonalDetailsComplete = (props) => {
const {
+1 -1
View File
@@ -32,7 +32,7 @@ import {
} from "../../store/watchedCases/action";
import { signIn, useSession } from "next-auth/react";
import { destroyCookie, parseCookies, setCookie } from "nookies";
import { destroyCookie, parseCookies, setCookie } from "../../lib/cookies";
import { getFormCollectionByID, getDetailsProxy, hasOwn } from "../utils";
import {
getLivePublishedEvent,
+1 -1
View File
@@ -2,7 +2,7 @@ import { signOut } from "next-auth/react";
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import { destroyCookie, parseCookies, setCookie } from "nookies";
import { destroyCookie, parseCookies, setCookie } from "../../lib/cookies";
import { signIn, useSession } from "next-auth/react";
const WatchModal = (props) => {
+1 -1
View File
@@ -1,7 +1,7 @@
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import { useState } from "react";
import { connect } from "react-redux";
import { reduxForm } from "redux-form";
+1 -1
View File
@@ -2,7 +2,7 @@ import _ from "lodash";
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import { destroyCookie, setCookie } from "nookies";
import { destroyCookie, setCookie } from "../lib/cookies";
import { connect } from "react-redux";
import Banner from "../components/banner";
import { setLogout } from "../store/accountDetails/action";
+1 -1
View File
@@ -2,7 +2,7 @@ import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { connect } from "react-redux";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import { setLoggedInUserId } from "../../store/accountDetails/action";
import { signOut, useSession } from "next-auth/react";
import CaseComment from "./casecomment";
@@ -1,7 +1,7 @@
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import { connect } from "react-redux";
import {
deleteAwaitingSubmissionsFromBlob,
+1 -1
View File
@@ -2,7 +2,7 @@ import _ from "lodash";
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import { connect } from "react-redux";
import {
deleteAwaitingSubmissions,
+1 -1
View File
@@ -1,7 +1,7 @@
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import { connect } from "react-redux";
import {
deleteAwaitingSubmissions,
+1 -1
View File
@@ -3,7 +3,7 @@ import _ from "lodash";
import useTranslation from "next-translate/useTranslation";
import Link from "next/link";
import { useRouter } from "next/router";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import { useState } from "react";
import { connect } from "react-redux";
import {
+1 -1
View File
@@ -8,7 +8,7 @@ import { connect } from "react-redux";
import transLookup from "../../data/lookuptranslations.json";
import { setCurrentReference } from "../../store/currentView/action";
import { parseCookies, setCookie } from "nookies";
import { parseCookies, setCookie } from "../../lib/cookies";
import { useEffect } from "react";
import { setLoggedInUserId } from "../../store/accountDetails/action";
import {
+1 -1
View File
@@ -15,7 +15,7 @@ import {
} from "../../actions/services/searchService";
import GoogleMapReact from "google-map-react";
import { parseCookies } from "nookies";
import { parseCookies } from "../../lib/cookies";
import OSPoint from "ospoint";
import {
createWatchedCases,
+1 -1
View File
@@ -14,7 +14,7 @@ import {
setCurrentReference
} from "../../store/currentView/action";
import { parseCookies, setCookie } from "nookies";
import { parseCookies, setCookie } from "../../lib/cookies";
import { useEffect } from "react";
import { setLoggedInUserId } from "../../store/accountDetails/action";
import {
+1 -1
View File
@@ -1,4 +1,4 @@
import { destroyCookie } from "nookies";
import { destroyCookie } from "../cookies";
import { signOut } from "next-auth/react";
const AUTH_COOKIE_CANDIDATES = [
+120
View File
@@ -0,0 +1,120 @@
import * as cookie from "cookie";
import setCookieParser from "set-cookie-parser";
const isBrowser = () => typeof window !== "undefined";
const createCookie = (name, value, options) => {
let sameSite = options.sameSite;
if (sameSite === true) {
sameSite = "strict";
}
if (sameSite === undefined || sameSite === false) {
sameSite = "lax";
}
const cookieToSet = { ...options, sameSite };
delete cookieToSet.encode;
return { name, value, ...cookieToSet };
};
const hasSameProperties = (firstCookie, secondCookie) => {
const firstProperties = Object.getOwnPropertyNames(firstCookie);
const secondProperties = Object.getOwnPropertyNames(secondCookie);
if (firstProperties.length !== secondProperties.length) {
return false;
}
return firstProperties.every(
(propertyName) =>
firstCookie[propertyName] === secondCookie[propertyName]
);
};
const areCookiesEqual = (firstCookie, secondCookie) => {
let sameSiteMatches = firstCookie.sameSite === secondCookie.sameSite;
if (
typeof firstCookie.sameSite === "string" &&
typeof secondCookie.sameSite === "string"
) {
sameSiteMatches =
firstCookie.sameSite.toLowerCase() ===
secondCookie.sameSite.toLowerCase();
}
return (
hasSameProperties(
{ ...firstCookie, sameSite: undefined },
{ ...secondCookie, sameSite: undefined }
) && sameSiteMatches
);
};
export const parseCookies = (ctx, options) => {
if (ctx?.req?.headers?.cookie) {
return cookie.parse(ctx.req.headers.cookie, options);
}
if (isBrowser()) {
return cookie.parse(document.cookie, options);
}
return {};
};
export const setCookie = (ctx, name, value, options = {}) => {
if (ctx?.res?.getHeader && ctx.res.setHeader) {
if (ctx?.res?.finished) {
console.warn(`Not setting "${name}" cookie. Response has finished.`);
console.warn("You should set cookie before res.send()");
return {};
}
let cookies = ctx.res.getHeader("Set-Cookie") || [];
if (typeof cookies === "string") {
cookies = [cookies];
}
if (typeof cookies === "number") {
cookies = [];
}
const parsedCookies = setCookieParser.parse(cookies, {
decodeValues: false
});
const newCookie = createCookie(name, value, options);
const cookiesToSet = [];
parsedCookies.forEach((parsedCookie) => {
if (!areCookiesEqual(parsedCookie, newCookie)) {
cookiesToSet.push(
cookie.serialize(parsedCookie.name, parsedCookie.value, {
encode: (cookieValue) => cookieValue,
...parsedCookie
})
);
}
});
cookiesToSet.push(cookie.serialize(name, value, options));
ctx.res.setHeader("Set-Cookie", cookiesToSet);
}
if (isBrowser()) {
if (options?.httpOnly) {
throw new Error("Can not set a httpOnly cookie in the browser.");
}
document.cookie = cookie.serialize(name, value, options);
}
return {};
};
export const destroyCookie = (ctx, name, options) =>
setCookie(ctx, name, "", { ...(options || {}), maxAge: -1 });
+13 -34
View File
@@ -21,6 +21,7 @@
"@xmldom/xmldom": "^0.8.10",
"applicationinsights": "^2.9.1",
"axios": "^1.21.0",
"cookie": "0.7.2",
"cookie-cutter": "^0.2.0",
"crypto-js": "^4.2.0",
"date-fns": "^2.30.0",
@@ -46,7 +47,6 @@
"next-translate": "^2.6.2",
"next-translate-plugin": "^2.6.2",
"nodemailer": "7.0.13",
"nookies": "^2.5.2",
"notifications-node-client": "^7.0.6",
"openapi-types": "^12.1.3",
"ospoint": "^0.2.1",
@@ -73,6 +73,7 @@
"redux-thunk": "^2.4.2",
"sass": "1.69",
"sass-loader": "13.3.2",
"set-cookie-parser": "2.7.0",
"tslib": "^2.7.0",
"typescript": "^5.3.2",
"uuid": "9.0.0",
@@ -3716,6 +3717,15 @@
"emitter-listener": "^1.1.1"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-cutter": {
"version": "0.2.0",
"license": "MIT"
@@ -4919,14 +4929,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -6940,14 +6942,6 @@
}
}
},
"node_modules/next-auth/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/next-auth/node_modules/preact": {
"version": "10.24.0",
"license": "MIT",
@@ -7101,23 +7095,6 @@
"node": ">=6.0.0"
}
},
"node_modules/nookies": {
"version": "2.5.2",
"license": "MIT",
"dependencies": {
"cookie": "^0.4.1",
"set-cookie-parser": "^2.4.6"
}
},
"node_modules/nookies/node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"license": "MIT",
@@ -8629,6 +8606,8 @@
},
"node_modules/set-cookie-parser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",
"integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
+2 -1
View File
@@ -43,6 +43,7 @@
"@xmldom/xmldom": "^0.8.10",
"applicationinsights": "^2.9.1",
"axios": "^1.21.0",
"cookie": "0.7.2",
"cookie-cutter": "^0.2.0",
"crypto-js": "^4.2.0",
"date-fns": "^2.30.0",
@@ -68,7 +69,6 @@
"next-translate": "^2.6.2",
"next-translate-plugin": "^2.6.2",
"nodemailer": "7.0.13",
"nookies": "^2.5.2",
"notifications-node-client": "^7.0.6",
"openapi-types": "^12.1.3",
"ospoint": "^0.2.1",
@@ -95,6 +95,7 @@
"redux-thunk": "^2.4.2",
"sass": "1.69",
"sass-loader": "13.3.2",
"set-cookie-parser": "2.7.0",
"tslib": "^2.7.0",
"typescript": "^5.3.2",
"uuid": "9.0.0",
+1 -1
View File
@@ -2,7 +2,7 @@ import Document, { Html, Head, Main, NextScript } from "next/document";
import SkipLink from "../components/skiplink";
import { Tracking, TrackingNoScript } from "../components/tracking";
//import { nanoid } from "nanoid";
import { setCookie } from "nookies";
import { setCookie } from "../lib/cookies";
import Script from "next/script";
class MyDocument extends Document {
+1 -1
View File
@@ -4,7 +4,7 @@ import Link from "next/link";
import CookieBanner from "../components/cookieBanner";
import Footer from "../components/footer";
import Header from "../components/header";
import { parseCookies, setCookie, destroyCookie } from "nookies";
import { parseCookies, setCookie, destroyCookie } from "../lib/cookies";
import { useSession, signIn, signOut } from "next-auth/react";
function Error({ statusCode }, props) {
+1 -1
View File
@@ -20,7 +20,7 @@ import { reduxForm, formValueSelector } from "redux-form";
import PersonalDetails from "../../components/account/personaldetails";
import { setAccountDetails } from "../../store/accountDetails/action";
import { getPersonalAccount } from "../../actions/services/accountService";
import { setCookie } from "nookies";
import { setCookie } from "../../lib/cookies";
import { getSession, useSession } from "next-auth/react";
import NoSessionWarning from "../../components/nosession";
+1 -1
View File
@@ -7,7 +7,7 @@ import CookieBanner from "../../components/cookieBanner";
import Footer from "../../components/footer";
import Header from "../../components/header";
import ServiceBanner from "../../components/servicebanner";
import { setCookie } from "nookies";
import { setCookie } from "../../lib/cookies";
const SignIn = (props) => {
let { t, lang } = useTranslation();
+1 -1
View File
@@ -2,7 +2,7 @@ import { signOut, useSession } from "next-auth/react";
import useTranslation from "next-translate/useTranslation";
import Head from "next/head";
import Router from "next/router";
import { destroyCookie } from "nookies";
import { destroyCookie } from "../lib/cookies";
import CookieBanner from "../components/cookieBanner";
import Footer from "../components/footer";
import Header from "../components/header";
+1 -1
View File
@@ -3,7 +3,7 @@ import { getSession } from "next-auth/react";
import useTranslation from "next-translate/useTranslation";
import Head from "next/head";
import { useRouter } from "next/router";
import { setCookie } from "nookies";
import { setCookie } from "../lib/cookies";
import { connect } from "react-redux";
import { hashString } from "../actions/core/hash";
import { getIP } from "../actions/core/logger";
@@ -0,0 +1,331 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const vm = require("node:vm");
const rootDir = path.resolve(__dirname, "..", "..");
const normalize = (value) => JSON.parse(JSON.stringify(value));
const loadLocalCookieCompatibility = () => {
const filePath = path.join(rootDir, "lib", "cookies.js");
let source = fs.readFileSync(filePath, "utf8");
source = source.replace(
'import * as cookie from "cookie";',
'const cookie = require("cookie");'
);
source = source.replace(
'import setCookieParser from "set-cookie-parser";',
'const setCookieParser = require("set-cookie-parser");'
);
source = source.replace(/export const\s+/g, "const ");
source += "\nmodule.exports = { parseCookies, setCookie, destroyCookie };\n";
const context = {
module: { exports: {} },
exports: {},
require,
console,
document: global.document,
window: global.window
};
vm.runInNewContext(source, context, { filename: filePath });
return context.module.exports;
};
const loadCookieImplementation = () => {
return loadLocalCookieCompatibility();
};
const withBrowserDocument = async (initialCookies, callback) => {
const previousDocument = global.document;
const previousWindow = global.window;
const cookieJar = new Map(Object.entries(initialCookies));
const writes = [];
const documentMock = {};
Object.defineProperty(documentMock, "cookie", {
configurable: true,
get() {
return Array.from(cookieJar.entries())
.map(([name, value]) => `${name}=${value}`)
.join("; ");
},
set(serializedCookie) {
writes.push(serializedCookie);
const [nameValue, ...attributes] = serializedCookie.split(";");
const separatorIndex = nameValue.indexOf("=");
const name = nameValue.slice(0, separatorIndex);
const value = nameValue.slice(separatorIndex + 1);
const maxAge = attributes
.map((attribute) => attribute.trim())
.find((attribute) =>
attribute.toLowerCase().startsWith("max-age=")
);
if (maxAge && Number(maxAge.split("=")[1]) < 0) {
cookieJar.delete(name);
return;
}
cookieJar.set(name, value);
}
});
global.window = {};
global.document = documentMock;
try {
await callback({ cookieJar, writes });
} finally {
if (previousWindow === undefined) {
delete global.window;
} else {
global.window = previousWindow;
}
if (previousDocument === undefined) {
delete global.document;
} else {
global.document = previousDocument;
}
}
};
const createResponse = (initialHeader) => {
let setCookieHeader = initialHeader;
const calls = [];
return {
response: {
finished: false,
getHeader(name) {
assert.strictEqual(name, "Set-Cookie");
return setCookieHeader;
},
setHeader(name, value) {
calls.push({ name, value });
setCookieHeader = value;
}
},
calls,
getSetCookieHeader: () => setCookieHeader
};
};
test("browser parsing preserves characterized nookies behavior", async () => {
await withBrowserDocument(
{
first: "one",
encoded: "two%20words",
equals: "a%3Db",
spaces: "hello%20world",
special: "%21%40%23%24%25%5E%26*%28%29",
malformed: "%E0%A4%A"
},
() => {
const { parseCookies } = loadCookieImplementation();
assert.deepStrictEqual(parseCookies(), {
first: "one",
encoded: "two words",
equals: "a=b",
spaces: "hello world",
special: "!@#$%^&*()",
malformed: "%E0%A4%A"
});
}
);
await withBrowserDocument({}, () => {
const { parseCookies } = loadCookieImplementation();
assert.deepStrictEqual(parseCookies(), {});
});
});
test("parseCookies returns an empty object without a browser document", () => {
const { parseCookies } = loadCookieImplementation();
assert.deepStrictEqual(normalize(parseCookies()), {});
assert.deepStrictEqual(normalize(parseCookies(null)), {});
assert.deepStrictEqual(normalize(parseCookies({})), {});
});
test("browser writes preserve encoding and supported options", async () => {
await withBrowserDocument({}, ({ writes }) => {
const { setCookie } = loadCookieImplementation();
const expires = new Date("2030-01-02T03:04:05.000Z");
assert.deepStrictEqual(
normalize(setCookie(null, "basic", "value")),
{}
);
setCookie(null, "pathCookie", "value", { path: "/" });
setCookie(null, "expiresCookie", "value", { expires });
setCookie(null, "maxAgeCookie", "value", { maxAge: 60 });
setCookie(null, "encodedCookie", "hello world=a&b");
assert.deepStrictEqual(writes, [
"basic=value",
"pathCookie=value; Path=/",
"expiresCookie=value; Expires=Wed, 02 Jan 2030 03:04:05 GMT",
"maxAgeCookie=value; Max-Age=60",
"encodedCookie=hello%20world%3Da%26b"
]);
});
});
test("browser writes reject httpOnly cookies", async () => {
await withBrowserDocument({}, () => {
const { setCookie } = loadCookieImplementation();
assert.throws(
() => setCookie(null, "protected", "value", { httpOnly: true }),
/Can not set a httpOnly cookie in the browser\./
);
});
});
test("browser deletion preserves path and uses an empty value with negative maxAge", async () => {
await withBrowserDocument(
{
pedwWatchCase: "case-1",
pinsUser: "contact-1",
pedw_locale: "cy"
},
({ cookieJar, writes }) => {
const { destroyCookie } = loadCookieImplementation();
for (const name of ["pedwWatchCase", "pinsUser", "pedw_locale"]) {
assert.deepStrictEqual(
normalize(destroyCookie(null, name, { path: "/" })),
{}
);
}
assert.deepStrictEqual(writes, [
"pedwWatchCase=; Max-Age=-1; Path=/",
"pinsUser=; Max-Age=-1; Path=/",
"pedw_locale=; Max-Age=-1; Path=/"
]);
assert.strictEqual(cookieJar.has("pedwWatchCase"), false);
assert.strictEqual(cookieJar.has("pinsUser"), false);
assert.strictEqual(cookieJar.has("pedw_locale"), false);
}
);
});
test("server parsing uses ctx.req.headers.cookie", () => {
const { parseCookies } = loadCookieImplementation();
assert.deepStrictEqual(
parseCookies({
req: {
headers: {
cookie: "first=one; encoded=two%20words; equals=a%3Db"
}
}
}),
{
first: "one",
encoded: "two words",
equals: "a=b"
}
);
});
test("server writes a Set-Cookie header and preserves unrelated cookies", () => {
const { setCookie } = loadCookieImplementation();
const responseState = createResponse([
"unrelated=keep; Path=/; SameSite=Lax",
"other=value; HttpOnly; Path=/; SameSite=Strict"
]);
assert.deepStrictEqual(
normalize(
setCookie(
{ res: responseState.response },
"target",
"new value",
{ path: "/" }
)
),
{}
);
assert.deepStrictEqual(normalize(responseState.getSetCookieHeader()), [
"unrelated=keep; Path=/; SameSite=Lax",
"other=value; Path=/; HttpOnly; SameSite=Strict",
"target=new%20value; Path=/"
]);
assert.strictEqual(responseState.calls.length, 1);
});
test("server deduplicates an exactly matching cookie and keeps cookies with changed values or paths", () => {
const { setCookie } = loadCookieImplementation();
const responseState = createResponse([
"target=new; Path=/; SameSite=Lax",
"target=old; Path=/; SameSite=Lax",
"target=keep-a; Path=/a; SameSite=Lax",
"target=keep-b; Path=/b; SameSite=Lax"
]);
setCookie({ res: responseState.response }, "target", "new", {
path: "/",
sameSite: "lax"
});
assert.deepStrictEqual(normalize(responseState.getSetCookieHeader()), [
"target=old; Path=/; SameSite=Lax",
"target=keep-a; Path=/a; SameSite=Lax",
"target=keep-b; Path=/b; SameSite=Lax",
"target=new; Path=/; SameSite=Lax"
]);
});
test("server does not write after the response has finished", () => {
const { setCookie } = loadCookieImplementation();
const responseState = createResponse();
responseState.response.finished = true;
const warnings = [];
const originalWarn = console.warn;
console.warn = (message) => warnings.push(message);
try {
assert.deepStrictEqual(
normalize(
setCookie(
{ res: responseState.response },
"target",
"value"
)
),
{}
);
} finally {
console.warn = originalWarn;
}
assert.deepStrictEqual(responseState.calls, []);
assert.deepStrictEqual(warnings, [
'Not setting "target" cookie. Response has finished.',
"You should set cookie before res.send()"
]);
});
test("server no-op behavior is preserved for null, empty, and undefined contexts", () => {
const { setCookie, destroyCookie } = loadCookieImplementation();
assert.deepStrictEqual(normalize(setCookie(null, "target", "value")), {});
assert.deepStrictEqual(normalize(setCookie({}, "target", "value")), {});
assert.deepStrictEqual(
normalize(setCookie(undefined, "target", "value")),
{}
);
assert.deepStrictEqual(
normalize(destroyCookie({}, "pedwWatchCase", { path: "/" })),
{}
);
});