
Effect 4 Cookie 校验机制深入解析构造与序列化前的名称、域与路径验证【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect本文聚焦 Effect 4effect包HTTP 模块中的 Cookie 校验能力在构造或序列化 Cookie 之前对名称name、域domain与路径path等进行严格的合法性校验从源头阻断 Cookie 注入Cookie Injection / Header Splitting类安全风险。读者读完本文后将掌握Cookies模块的校验规则、错误模型、Result风格的安全 API以及它在 HTTP 服务端响应与 Schema 编解码中的实际用法。该能力来自本次变更.changeset/pre/eff-210-cookie-validation.md对应effect包的patch级更新Validate cookie names, domains, and paths before constructing or serializing cookies.它在packages/effect/CHANGELOG.md中有同源记录PR #6763测试见 packages/effect/test/unstable/http/Cookies.test.ts。一、为什么需要校验Cookie 注入的根源HTTP Cookie 通过Set-Cookie响应头与Cookie请求头传输其语法是键值对 分号分隔的属性列表。如果应用直接拼接用户可控输入构造Set-Cookie恶意输入中的;、换行符等分隔符就会逃逸出预期字段产生两类典型攻击Cookie 注入在名称或域中插入; Domainevil.com;攻击者可以伪造出指向其他域名的 Cookie 属性响应头注入在值中插入 CR/LF 换行进而注入任意 HTTP 响应头。Effect 的应对策略简单而彻底在 Cookie 被构造makeCookie与序列化serializeCookie两个必经关口执行集中校验凡是不符合规范的名称、域、路径一律拒绝绝不进入后续的头部生成流程。这也正是本次 changeset 的核心主张——在构造或序列化 Cookie 之前进行验证保证两条路径上没有任何漏网之鱼。二、校验规则与判定标准校验逻辑集中在Cookies.ts内部的validateCookie函数与四个正则表达式上packages/effect/src/unstable/http/Cookies.ts#L344-L413// oxlint-disable-next-line no-control-regex const fieldContentRegExp /^[\u0009\u0020-\u007e\u0080-\u00ff]$/ const cookieNameRegExp /^[!#$%*\-.^_|~0-9A-Za-z]$/ // oxlint-disable-next-line no-control-regex const cookieDomainRegExp /^[\u0009\u0020-\u003a\u003c-\u007e\u0080-\u00ff]$/ const cookiePathRegExp /^[\u0020-\u003a\u003c-\u007e]$/对应五类校验规则逐条说明校验项判定正则规则说明失败原因标签名称 namecookieNameRegExp必须完全匹配RFC 6265 token字符集字母、数字以及!#$%*-.^_|~不允许空格、分号、等号、引号等分隔符 |InvalidCookieName值 valuefieldContentRegExp对encodeURIComponent编码后的值做检查只允许 ASCII 可见字符、制表符与高字节字符空值合法InvalidCookieValue域 domaincookieDomainRegExp排除冒号0x3a与控制字符防止;、等属性分隔符混入InvalidCookieDomain路径 pathcookiePathRegExp允许路径可见字符排除;与防止属性注入InvalidCookiePath最大存活 maxAgeDuration.isFinite拒绝Infinity等非有限时长CookieInfinityMaxAge值得注意的边界设计值校验发生在编码之后makeCookie先执行encodeURIComponent(value)得到valueEncoded再对编码结果做fieldContentRegExp校验Cookies.ts#L374-L391。这一顺序确保了通过该模块构造的 Cookie 值在传输层面是绝对安全的maxAge除了拒绝无穷大还会在序列化时取整serializeCookie将Duration转换为秒并执行Math.trunc输出整数秒的Max-AgeCookies.ts#L720-L723。三、错误模型CookiesError与CookiesErrorReason校验失败不是静默忽略而是通过显式错误类型传递便于调用方精确诊断与降级处理Cookies.ts#L80-L136export class CookiesErrorReason extends Data.Error{ readonly _tag: | InvalidCookieName | InvalidCookieValue | InvalidCookieDomain | InvalidCookiePath | CookieInfinityMaxAge readonly cause?: unknown } {} export class CookiesError extends Data.TaggedError(CookiesError){ readonly reason: CookiesErrorReason } { static fromReason(reason: CookiesError[reason][_tag], cause?: unknown): CookiesError { return new CookiesError({ reason: new CookiesErrorReason({ _tag: reason, cause }) }) } override get message() { return this.reason._tag } }错误模型的设计要点标签化原因reason._tag五选一调用方可以用switch/match精确区分失败类型message直接映射为原因标签错误信息的可读性与可诊断性合一序列化时抛出异常也能直接看出是哪一类校验失败类型标记CookiesError带有CookieErrorTypeId~effect/http/Cookies/CookieError可配合Cookies.isCookies等守卫做运行时类型判断。四、安全 API 全景构造、添加、过期与序列化本次变更塑造了整套先校验、再使用的 API 家族全部位于effect/unstable/http的Cookies命名空间下。4.1 构造 CookiemakeCookie与makeCookieUnsafemakeCookie是唯一合法构造入口返回Result.ResultCookie, CookiesError——校验失败进入Failure通道不抛异常import { Result } from effect import { Cookies } from effect/unstable/http // 合法输入RFC 6265 token 名称 规范域与路径 const ok Cookies.makeCookie(!#$%*-.^_|~, token, { domain: .sub.example.com, path: /some-path_with~chars/%20 }) // ResultCookie, CookiesError // 非法名称分号分隔符会触发 InvalidCookieName const badName Cookies.makeCookie(a; Domainevil.com; b, token) // 非法域属性分隔符触发 InvalidCookieDomain const badDomain Cookies.makeCookie(session, token, { domain: legit.com; Domain.parent.tld }) // 非法路径HttpOnly 字样被当作路径注入触发 InvalidCookiePath const badPath Cookies.makeCookie(session, token, { path: /; HttpOnly }) if (Result.isFailure(badName)) { console.log(badName.failure.reason._tag) // InvalidCookieName }makeCookieUnsafe是面向快速场景的变体内部调用Result.getOrThrow校验失败时直接抛出CookiesErrorCookies.ts#L421-L425。测试用例对上述三组非法输入均有断言Cookies.test.ts#L10-L40。4.2 向集合添加 Cookieset/setAll/setUnsafe/setAllUnsafeCookies是以 Cookie 名为键的不可变集合。添加操作同样区分安全与非安全变体set(self, name, value, options)返回Result.ResultCookies, CookiesError单个 Cookie 校验失败则整个操作失败setAll(self, tuples)批量版本任一元组非法即返回第一个CookiesError且保持原集合不变不产生部分更新Cookies.ts#L655-L679对应的setUnsafe/setAllUnsafe校验失败直接抛错。import { Cookies } from effect/unstable/http const result Cookies.set(Cookies.empty, session, abc123, { httpOnly: true, secure: true, path: /, sameSite: lax }) // ResultCookies, CookiesError4.3 序列化关口serializeCookie与toSetCookieHeaders校验的第二道关口是序列化。serializeCookie会再次执行validateCookie即使 Cookie 是通过fromIterable、setCookie等绕开构造校验的路径进入集合的序列化时也会被拦截Cookies.ts#L708-L713const invalidCookie { name: session, value: token, valueEncoded: token, options: { domain: legit.com; Domain.evil.com } } as unknown as Cookies.Cookie // 通过 fromIterable 或 setCookie 绕过构造校验 const cookies Cookies.fromIterable([invalidCookie]) // 序列化时抛错/InvalidCookieDomain/ Cookies.toSetCookieHeaders(cookies)对应的回归测试见 Cookies.test.ts#L55-L74覆盖了fromIterable与setCookie两条旁路。toSetCookieHeaders内部即Object.values(self.cookies).map(serializeCookie)Cookies.ts#L813因此对集合内每个 Cookie 都会触发二次校验。序列化时各属性的输出顺序与格式Max-Age、Domain、Path、Priority、Expires、HttpOnly、Secure、Partitioned、SameSite在 Cookies.ts#L719-L777 中定义。4.4 安全过期expireCookie/expireCookieUnsafe删除 Cookie 的正确姿势是下发一个立即过期的Set-Cookie。expireCookie自动写入空值、Max-Age0与纪元时间ExpiresThu, 01 Jan 1970 00:00:00 GMT同时复用校验逻辑名称非法同样进入Result失败通道const result Cookies.expireCookie(Cookies.empty, session, { path: /, secure: true }) // ResultCookies, CookiesError序列化为 session; Max-Age0; Path/; ExpiresThu, 01 Jan 1970 00:00:00 GMT; Secure五、在 HTTP 服务端响应中的集成校验能力通过HttpServerResponse暴露给实际业务代码packages/effect/src/unstable/http/HttpServerResponse.tsHttpServerResponse.setCookie(name, value, options)返回EffectHttpServerResponse, CookiesError内部即Effect.fromResult(Cookies.set(self.cookies, ...))将Result失败通道无缝映射为Effect失败通道HttpServerResponse.ts#L614-L637HttpServerResponse.expireCookie(name, options)同样返回EffectHttpServerResponse, CookiesErrorHttpServerResponse.ts#L657-L677另有setCookieUnsafe、expireCookieUnsafe、removeCookie等便捷变体以及整体替换 Cookie 集合的setCookies。在 Effect 的应用代码中非法 Cookie 输入会作为类型化错误进入Effect的失败通道配合Effect.catchTag即可实现精确的降级处理例如import { Effect } from effect import { HttpServerResponse } from effect/unstable/http const program HttpServerResponse.setCookie(session, userToken, { httpOnly: true, secure: true, path: /, sameSite: lax }).pipe( Effect.catchTag(CookiesError, (err) Effect.succeed( HttpServerResponse.text(bad cookie: ${err.reason._tag}, { status: 400 }) ) ) )六、解析路径上的防御fromSetCookie忽略非法名称校验不仅覆盖写出也覆盖读入。parseSetCookie在解析Set-Cookie头时会用cookieNameRegExp检查名称不合法的 Cookie 名直接返回undefined被丢弃而不是把脏数据带入集合Cookies.ts#L197-L210const cookies Cookies.fromSetCookie([ bad namevalue, // 名称含空格不符合 RFC 6265 token被忽略 sessionabc ]) Cookies.get(cookies, bad name) // Option.none() Cookies.getValue(cookies, session) // Option.some(abc)该行为与 CHANGELOG 中另一条记录一致IgnoreSet-Cookieheaders whose cookie names do not satisfy the RFC 6265 token syntaxCHANGELOG.md#L779并在 Cookies.test.ts#L43-L53 中有对应断言。读取侧Cookie请求头的解析由parseHeader完成同样源自 fastify-cookieMIT 许可Cookies.ts#L825-L862并对%编码的值做容错解码。七、与 Schema 的联动Schema.Cookies与RecordFromCookiesCookie 集合已深度集成到Schema模块packages/effect/src/Schema.ts#L12966-L13050Schema.Cookie/Schema.Cookies运行时类型即Cookies.Cookie/Cookies.Cookies。Cookies的 JSON 编码使用Set-Cookie头字符串数组decode 走fromSetCookie、encode 走toSetCookieHeaders因此编码路径同样会触发serializeCookie的二次校验isomorphic 编码则使用Record(string, Cookie)结构Schema.RecordFromCookies将 Cookie 集合与名称 → 解码后值的普通 Record 互转。配合Schema.toIso可对 Cookie 集合做类型安全的字段提取例如Schema.toIso(Schema.Cookies).at(sessionId)见 Cookies.test.ts#L104-L116import { Schema } from effect import { Cookies } from effect/unstable/http const cookies Cookies.fromSetCookie([ sessionIdabc123; Path/; HttpOnly; Secure, themedark; Path/; Max-Age3600 ]) Schema.decodeSync(Schema.RecordFromCookies)(cookies) // { sessionId: abc123, theme: dark } Schema.encodeSync(Schema.RecordFromCookies)({ session: abc }) // Cookies 集合可继续通过 toSetCookieHeaders 输出八、版本与使用前提该模块位于effect/unstable/http命名空间代码注释标注since 4.0.0属Effect 4 的不稳定unstableHTTP 能力API 可能在后续版本调整序列化逻辑注明Adapted from fastify/fastify-cookiePartitioned属性为 Chrome 2024 Q1 起的草案实现draft-cutler-httpbis-partitioned-cookiesCookies.ts#L759-L762使用时需注意浏览器兼容性本次校验增强是effect包的一个patch级变更随常规发布进入packages/effect/CHANGELOG.md#6763对既有合法 Cookie 使用场景完全向后兼容——被拒绝的仅是本就不符合 HTTP 规范、存在注入风险的输入。九、小结本次变更给 Effect 4 的 HTTP Cookie 处理补上了关键的安全闭环构造入口makeCookie/set/setAll与序列化出口serializeCookie/toSetCookieHeaders双重校验配合解析路径对非法名称的丢弃实现了进不来、写不出、读不进三道防线。校验失败通过CookiesError 标签化reason显式暴露并贯通到HttpServerResponse的Effect失败通道与Schema编解码链路让安全约束成为类型系统与错误模型的一部分而不是游离在外的临时检查。继续探索的入口核心实现packages/effect/src/unstable/http/Cookies.ts回归测试packages/effect/test/unstable/http/Cookies.test.ts服务端响应集成packages/effect/src/unstable/http/HttpServerResponse.tsSchema 联动packages/effect/src/Schema.tsSchema.Cookies/Schema.RecordFromCookies变更记录packages/effect/CHANGELOG.md#6763与.changeset/pre/eff-210-cookie-validation.md【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考