The Jerusalem PostIran split screen: When US, Israeli interests reach a fork in the road - analysisRTP DesportoBrandon McNulty fugiu para o arco-íris na prova de fundoESPN DeportesPumas se derrumba en casa y San Luis marca el terceroESPNUSC suspends LB Stephens after ejection for hit on Oregon QB MooreDaily MaverickUK PM Burnham refuses to back Heathrow’s third runway projectVanguardSerbia’s President resigns to run for PM in October electionBillboardFans Choose Madonna & Charli xcx’s ‘Danceteria Afterhours’ as This Week’s Favorite New MusicBBC NewsWhat we know about RAF base counter-terror investigationNOSRecordwarm einde van september op komstDeadlineBill Gates Warns Unregulated AI Development Could “Cause A Billion Deaths”Aitnewsمايكروسوفت تعيد تصميم Copilot.. تطبيق واحد يجمع الذكاء الاصطناعي وتطبيقات والبرمجةABC NewsUK police arrest 5 men under terrorism, explosives acts close to US air base
The Daily Newsstand · Free, Always
Sunday, September 27, 2026

Cookies, которых не видно: partitioned cookies, cookies.txt и одно расширение под три браузера

Translate

Бывает так: открываете менеджер cookies, удаляете всё для сайта, перезагружаете страницу — а сайт вас по-прежнему узнаёт. Одна из причин в том, что современные браузеры хранят часть cookies в отдельных «разделах», и старые расширения их просто не видят.

Я разбирался с этим, когда переписывал Cookie Quick Manager — популярный менеджер cookies для Firefox (около 45 тысяч пользователей на AMO), который не обновлялся с 2019 года. Результат — CookieTin, открытое расширение для Firefox, Chrome и Edge (GitHub). В статье — что такое partitioned cookies, как их достать через WebExtensions API и на какие грабли я наступил по дороге.

Что такое partitioned cookies

Раньше cookie стороннего сайта была одна на весь браузер. Если виджет widget.example был встроен в сотню сайтов, он видел одну и ту же cookie на каждом из них. На этом и держалась слежка между сайтами.

Сейчас браузеры это ломают:

  • Firefox, Total Cookie Protection (включена по умолчанию): cookies встроенного сайта хранятся отдельно для каждого сайта верхнего уровня. widget.example на news.example и тот же widget.example на shop.example получают две разные «банки» cookies.

  • Chrome, CHIPS (Cookies Having Independent Partitioned State): сайт сам помечает cookie атрибутом Partitioned, и она хранится отдельно для каждого сайта верхнего уровня.

В WebExtensions API такие cookies получили поле partitionKey:

{
  "name": "session",
  "domain": "widget.example",
  "partitionKey": { "topLevelSite": "https://news.example" }
}

Почему менеджеры их не видят

Вот главный подвох: cookies.getAll() по умолчанию partitioned cookies не возвращает. И в Firefox, и в Chrome. Никакой ошибки нет, их просто нет в выдаче.

Чтобы получить все cookies, в фильтр нужно передать пустой partitionKey. А в Firefox ещё и firstPartyDomain: null, иначе вы не увидите cookies режима First-Party Isolation:

export async function getAll(filter: Record<string, unknown> = {}): Promise<Cookie[]> {
  const base: Record<string, unknown> = { ...filter };
  if (isFirefox) base.firstPartyDomain = null;
  if (partitionQuerySupported !== false) {
    try {
      const list = await ext.cookies.getAll({ ...base, partitionKey: {} });
      partitionQuerySupported = true;
      return list;
    } catch (e) {
      // Старый Chromium не знает partitionKey в фильтре
      if (partitionQuerySupported === true) throw e;
      partitionQuerySupported = false;
    }
  }
  return await ext.cookies.getAll(base);
}

С удалением то же самое. cookies.remove() без точного partitionKey просто не найдёт cookie. Именно поэтому у пользователей Cookie Quick Manager некоторые cookies «не удалялись никак» (issue #121): расширение удаляло cookie с тем же именем и доменом, но из другого раздела.

Из этого следует ещё одна вещь, которую легко упустить: ключ уникальности cookie — не (domain, path, name). Две cookies с одинаковыми именем, доменом и путём могут одновременно жить в разных разделах и контейнерах. В CookieTin ключ такой:

[storeId, domain, path, name, firstPartyDomain, partitionKey.topLevelSite, partitionKey.hasCrossSiteAncestor]

В интерфейсе у таких cookies показывается, для какого сайта они хранятся. Когда вы удаляете cookies сайта из меню на панели, удаляются и partitioned cookies встроенных в него сайтов.

Как пересоздать cookie так, чтобы браузер её принял

Редактирование cookie в WebExtensions — это cookies.set() с новыми значениями. Кажется, что можно взять объект из getAll() и передать обратно. Но нет, по дороге есть несколько неочевидных правил.

Host-only cookie ставится без domain. Если передать domain: "example.com", браузер создаст cookie .example.com, которая видна поддоменам. Это уже другая cookie. Чтобы получить host-only, поле domain нужно вообще не передавать:

if (!c.hostOnly) d.domain = c.domain.startsWith(".") ? c.domain : "." + c.domain;

В Firefox нет sameSite: "unspecified". Chrome его принимает, Firefox выбрасывает ошибку. Решение — просто не передавать поле, тогда подставится значение по умолчанию.

Браузер отказывает без объяснений. Cookie __Host- с полем domain, SameSite=None без Secure или дата истечения в прошлом — и cookies.set() вернёт null или бросит невнятную ошибку. Поэтому в редакторе CookieTin эти правила проверяются до сохранения и объясняются человеческим языком.

cookies.txt: формат, который все пишут чуть по-разному

cookies.txt (формат Netscape) нужен для curl, wget, yt-dlp и gallery-dl. Формат простой — семь колонок через табуляцию:

.example.com	TRUE	/	TRUE	1893456000	session	abc123
#HttpOnly_example.com	FALSE	/	TRUE	0	token	xyz

Вторая колонка означает «включать поддомены» (TRUE/FALSE). HttpOnly-cookies пишутся с префиксом #HttpOnly_. Старые парсеры принимают такие строки за комментарии и теряют их.

Cookie Quick Manager писал эту колонку наоборот и строчными буквами: true у него означало «только этот хост». Из-за этого yt-dlp получал неправильные cookies (issue #96). При импорте CookieTin различает оба варианта по регистру. Реальные файлы из curl и браузеров пишут TRUE/FALSE, а старые файлы из CQM — true/false:

const cqm = flag === "true" || flag === "false";
const hostOnly = domain.startsWith(".") ? false : cqm ? flag === "true" : flag !== "TRUE";

Защищённые cookies

Одна из любимых функций у пользователей — «защитить cookie», чтобы очистка её не трогала. В CookieTin защищённая cookie ещё и возвращается на место, если её удалил сайт или вы сами в настройках браузера:

ext.cookies.onChanged.addListener(async (info) => {
  if (!info.removed || info.cause !== "explicit") return; // истекшие и перезаписанные не трогаем
  if (!settings.guardProtected || !isProtected(map, info.cookie)) return;
  await setCookie(info.cookie);
});

Фильтр cause === "explicit" важен. Без него расширение воскрешало бы и cookies с истёкшим сроком, и cookies, которые сайт просто перезаписал новым значением.

Один код на Firefox, Chrome и Edge

Расширение собирается под Manifest V3 из одной кодовой базы: TypeScript, Preact и esbuild. Различия браузеров спрятаны в нескольких местах:

  • Хранилища cookies. В Chrome это "0" (обычное) и "1" (инкогнито). В Firefox — firefox-default, firefox-private и отдельные хранилища контейнеров Multi-Account Containers (contextualIdentities).

  • Манифест. Для Firefox добавляется browser_specific_settings с ID расширения и data_collection_permissions (его теперь требует AMO), для Chrome — ничего лишнего.

  • Фоновый код. В MV3 фоновая страница засыпает. Все таймеры (периодическая очистка) переведены на chrome.alarms, а состояние живёт в storage, а не в памяти.

Отдельная история — пользователи старого расширения. Firefox позволяет выпустить новую версию под тем же ID дополнения. Для этого случая CookieTin умеет собираться с ID Cookie Quick Manager и при обновлении сам переносит защищённые cookies и настройки из старого формата хранения. Для тех, кто просто установит CookieTin рядом, есть импорт резервной копии CQM.

Тестирование

E2E-тесты на Puppeteer гоняются в Chrome for Testing, Firefox и Edge. Тест создаёт через API обычные, HttpOnly и partitioned cookies (с partitionKey: { topLevelSite: "https://example.com" }), открывает менеджер и проверяет всё подряд: список с пометкой раздела, удаление partitioned cookie (то, что не умел Cookie Quick Manager), редактирование, экспорт и импорт.

Самая неприятная часть — Firefox. Установить временное дополнение через WebDriver BiDi можно (installExtension), но чтобы открыть его страницы, нужен внутренний UUID расширения. Его приходится фиксировать через префы профиля (extensions.webextensions.uuids).

Отдельный тест проверяет обновление: ставит настоящий Cookie Quick Manager 0.5rc2 из исходников, добавляет защищённые cookies, обновляет до CookieTin и проверяет, что всё переехало.

Итог

  • Видит и удаляет partitioned cookies (TCP/CHIPS), cookies контейнеров и приватных окон.

  • Редактор проверяет правила браузера до сохранения, декодирует URL, Base64 и JWT.

  • Импорт и экспорт в JSON (Cookie-Editor, EditThisCookie), cookies.txt с правильной семантикой, заголовок Cookie, формат Cookie Quick Manager, Playwright.

  • Защищённые cookies, автоочистка по таймеру и при запуске.

  • Firefox (включая Android), Chrome, Edge. Без сети, аналитики и аккаунтов. Лицензия GPL-3.0, как у оригинала.

Код и релизы: https://github.com/Perruer/cookietin

Если расширение пригодится, поддержать разработку можно на Boosty.

Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.