不透明会话模型中,浏览器保存含不透明标识和存储期限的 Cookie。标识查找服务端保存用户状态和服务端期限的会话记录。退出时既清除 Cookie,也作废会话。

Cookie 与会话守住边界

Cookie 是浏览器的存储与传输机制。服务端会话则是一种应用设计,用 Cookie 的值查找服务器保存的状态。分开这两种责任,登录、过期和退出才容易验证。本文沿着不透明会话标识的生命周期展开,不再重复主机、路径或 SameSite 匹配规则;这些发送条件不能回答旧会话是否已经真正失效。 Cookie 期限控制浏览器存储,会话有效性和操作授权由服务端负责。替换并作废旧标识后,还要测试重放。两种计时示例使用原文实验单位,并未证明浏览器或分布式系统行为。 持有标识仍可能获得权限 在这种设计中,服务器生成不可预测的标识,并把它关联到一条会话记录。客户端不需要理解该值,但持有它仍可能获得访问资格:窃取有效 bearer 标识的人,可以尝试直接重放。称它“只是指针”不能让读者以为它无害。不要把它放入 URL、日志、诊断截图或支持材料中,随机字符串也可能是实际凭据。 不能直接信任客户端控制的明文角色字段。不过,Cookie 中含有有意义的数据,也不等于必然可以伪造:经过正确认证和签名的会话格式属于另一种设计。签名保护完整性,不提供保密性;无状态方案仍须明确过期、密钥管理与撤销策略。应选择维护良好的会话框架,不要仅为了省一次服务端查询就自行发明格式。 不透明方案需要使用密码学安全随机生成器和足够熵值。未知标识应被拒绝,而不能用客户端任意指定的值建立特权状态。服务器解析会话后,还须根据当前权限授权每项操作。某个会话已经认证,不代表它可以读取每一个对象、操作每一个账号,或自动获得所有管理功能;会话存在与具体动作获准是两项检查。 两种寿命需要两处控制 Cookie 的 Max-Age 与 Expires 管理客户端存储,不会自动使服务端会话记录过期。非浏览器客户端可以保留这个值,并在浏览器本该删除后继续重放。浏览器会话 Cookie 也可能通过会话恢复机制保留。因此必须在服务端强制执行绝对期限与空闲期限,使用明确的边界比较和一致的时间来源,而不能靠标签页里的倒计时作为安全判断。 退出时既要作废服务端状态,也要按匹配的作用域清除浏览器 Cookie,否则复制出来的令牌仍可能有效。认证后与权限变化后轮换标识,有助于防止会话固定,但前提是旧标识确实失去权限。并发请求和分布式存储需要额外协调,避免一个较晚完成的旧请求悄悄恢复已经撤销的会话;只覆盖单进程正常路径远远不够。 ...

一月 12, 2027 · 2 分钟 · XB Field Notes
只读根树分叉到 /usr、/etc,以及明确可写的 /tmp、/work 挂载。

只读根在其范围内限制文件系统修改

只读根文件系统限制对服务打包代码和配置所在文件系统层的普通写入,明确挂载仍可提供运行数据的可写位置。其边界是文件系统变更本身。它不会单独移除进程权限、阻止读取可访问数据,也不会禁止与其他服务通信,需要按实际作用范围理解其价值。 坏掉的样子 应用经常在主要数据目录之外写入临时文件、缓存、锁文件、生成配置或运行状态。只读根会让这些假设在启动或不常见操作中以 EROFS 暴露。只测健康检查端点,可能漏掉生成报表、刷新缓存或关停清理时才经过的写入路径。因此必须沿真实工作流程检查需求,不能把进程成功启动当成全部功能可用。 即使容器根层只读,可写挂载仍然可写,其中状态仍可能被故障或受损进程改坏。把大块可写目录覆盖到打包路径,也可能抵消这些路径原本需要的保护。应检查最终挂载布局,而不是根据一个根层选项推断整棵路径树的行为。路径看起来位于只读根之下,并不证明中间没有另一个可写挂载。 只读与保密也是不同属性。进程仍可能读取打包文件、执行已有程序,并通过允许的网络路径发送数据。该设置也不保证容器逃逸之后仍受相同保护,更不约束能够改变挂载安排的权限。应结合凭据、能力、隔离和应用行为理解它的贡献,避免把一项文件系统限制描述成完整安全边界。 只读根限制自身层的修改;可写挂载、读取、执行与本地通信仍需分别考虑。 快速验证 在一次性 Linux 容器内使用 root 身份,根层只读,/tmp 挂可写 tmpfs,/work 挂独立可写卷。镜像中必须已有 Python 3.7 或更新版本和 /etc/os-release,且断言保持启用。早期记录报告 Linux 6.12.76、Python 3.11.2,没有宿主机绑定挂载、特权模式或外部网络。这里没有复现该次运行;这些启动条件是前提,并非全部由脚本检查: from pathlib import Path from tempfile import mkdtemp import errno,os,socket,subprocess,sys,time,uuid def cleanup_steps(steps, primary): failures=[] for label, action in steps: try:action() except BaseException as error: failures.append(label+': '+type(error).__name__+': '+str(error)) if failures: message='cleanup incomplete or uncertain: '+'; '.join(failures) try:print(message,file=sys.stderr) except (OSError,ValueError):pass if primary is None:raise RuntimeError(message) return failures def remove_file(path): try:path.unlink() except FileNotFoundError:pass def require_empty(value): if value:raise RuntimeError('residual resources: '+str(value)) assert os.geteuid()==0,'run inside a disposable read-only-root container as root' assert os.statvfs('/').f_flag&os.ST_RDONLY name='xbcatm-readonly-'+uuid.uuid4().hex print('fixture root-tree probe:',name,flush=True) for base in [Path('/'),Path('/usr'),Path('/etc')]: path=base/name fd=None try: try:fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) except OSError as error:assert error.errno==errno.EROFS,error else:raise AssertionError('root filesystem accepted a new file') finally: primary=sys.exc_info()[1] if fd is not None: cleanup_steps([('probe descriptor',lambda:os.close(fd)),('unexpected root-tree file '+str(path),lambda:remove_file(path))],primary) print('even this root process receives EROFS on paths in the read-only root, usr and etc tree') for base in ['/tmp','/work']: assert not os.statvfs(base).f_flag&os.ST_RDONLY directory=None try: directory=Path(mkdtemp(prefix='xbcatm-write-',dir=base)) path=directory/'data';path.write_bytes(b'allowed');assert path.read_bytes()==b'allowed' path.write_bytes(b'changed');assert path.read_bytes()==b'changed' finally: primary=sys.exc_info()[1] if directory is not None: cleanup_steps([('temporary data',lambda:remove_file(directory/'data')),('temporary directory',directory.rmdir)],primary) print('explicit tmpfs and data-volume mounts remain writable, including overwriting their contents') assert Path('/etc/os-release').read_text() result=subprocess.run([sys.executable,'-c','print("executed")'],capture_output=True,text=True,check=True,timeout=5) assert result.stdout.strip()=='executed' def remaining(deadline): seconds=deadline-time.monotonic() if seconds<=0:raise TimeoutError('loopback exchange deadline expired') return seconds def receive_exact(connection,count,deadline): chunks=[];received=0 while received<count: connection.settimeout(remaining(deadline)) chunk=connection.recv(count-received) if not chunk:raise EOFError('loopback peer closed before the complete payload') chunks.append(chunk);received+=len(chunk) return b''.join(chunks) listener=client=connection=None try: deadline=time.monotonic()+5 listener=socket.socket();listener.settimeout(remaining(deadline)) listener.bind(('127.0.0.1',0));listener.listen(1) client=socket.socket();client.settimeout(remaining(deadline));client.connect(listener.getsockname()) listener.settimeout(remaining(deadline));connection,_=listener.accept() client.settimeout(remaining(deadline));client.sendall(b'local') assert receive_exact(connection,5,deadline)==b'local' finally: primary=sys.exc_info()[1] cleanup_steps([(label,sock.close) for label,sock in [('connection',connection),('client',client),('listener',listener)] if sock is not None],primary) print('reading packaged files, executing an existing program, and loopback network I/O still work') 脚本先检查根文件系统报告的只读标志,再尝试在根、usr 和 etc 下排他创建唯一名字的文件。即使采用 root 身份,三次都得到 EROFS。唯一名称与排他创建保证探测不会覆盖既有配置文件。结果来自当前文件系统真正拒绝操作,而不是只看配置中出现了某个只读选项。 ...

十二月 23, 2026 · 2 分钟 · XB Field Notes
身份提供方认证用户,将 ID Token 交给应用边界。应用验证令牌并绑定登录请求,继续管理自己的会话与访问决定。ID Token 过期不会自动结束应用会话。

单点登录委托认证,应用仍需验证

单点登录让应用依赖身份提供方的认证结果,用户不必把提供方的密码交给每一个应用。应用仍要负责自己的授权决定、本地会话和接入配置;机密客户端还可能持有客户端凭据。委托认证不会消除应用的安全边界,也不保证以后每次登录都无需用户交互。是否需要重新认证,仍然受提供方策略、当前会话和应用要求影响。 SSO 将认证结果传入应用自身的安全边界。应用仍要验证令牌、绑定请求并管理会话和访问。解码不能建立可信身份;ID Token 的过期不会自动结束一个独立的应用会话。 先分清协议与令牌 SAML 与 OpenID Connect 都可支持联合身份,但它们是不同协议,不能简化成可以互换的 XML 与 JSON 编码。本文聚焦 OIDC:它在 OAuth 2.0 之上定义身份层,通过 ID Token 表达认证信息。访问令牌用于访问资源,格式也可能不同。一个看起来像 JWT 的字符串并不自动成为 ID Token;资源服务器更不能因为认识签发者,就把任意一种令牌当作访问许可。 ...

十一月 17, 2026 · 2 分钟 · XB Field Notes
通过验证的 TLS 1.3 分支到四份响应。200 与 ready 标有对勾;200 与 different service、503 unavailable 及 302 明文 Location 均标有叉号。

检查 HTTPS,而不只是那把锁

HTTPS 保护一条连接,并按照客户端的信任规则认证对端。但它不会证明网站意图、业务正确性或服务可用性。地址栏的安全指示既不只是“字节已加密”的标签,也不是完整健康报告。有效检查应分别判断 TLS 连接、HTTP 响应,以及服务实际应该提供的行为,不能把三个问题合成一个模糊的绿灯。 分别提出具体问题 首先确认目标与身份。连接预期入口的同时,应按照证书 SAN 和可接受的信任链验证预期主机名。IP 地址正确,或者能够打印证书主题,都不足以完成身份验证。为了让探针通过而关闭验证,只会移除原本想取得的证据,并不能证明对端已经变得正确。 接着检查有效时间和协商协议。叶证书的到期日期仍在未来,不代表整条证书链可用;一次 TLS 协商成功,也只说明这个客户端实际选中了哪个版本和套件,并没有枚举服务端可能接受的全部配置。配置或库升级之后,仍应另外验证明确的允许和拒绝策略,不能用一次成功连接概括全部安全条件。 最后定义应用成功的条件。HTTP 状态、预期内容、重定向和相关依赖各自回答不同问题。静态站也可能提供有效证书,却返回不存在的页面、旧版本内容或上游错误。即使应用只负责发送文件,TLS 也不是整段访问的全部,更不能替代页面和发布结果的检查。 坏掉的样子 HTTP 库收到一个可正常读取的 503 响应时,未必会把它当成传输错误。若探针只检查 err 是否为空,就可能把不可用服务判断为健康。同样,HTTP 200 也可能来自错误虚拟主机、登录页面或被缓存的错误文档,因此应核验稳定、属于目标服务自身的响应约定。 自动跟随重定向可能隐藏真正被检查的入口。最初的 HTTPS 响应可能指向 HTTP 或非预期地址,最终只看到一个 200 就会丢失中途发生的变化。应依据明确策略检查跳转链,不能为了把请求跑通,就顺手把凭据转发到新的目标地址。 TLS 终止位置同样重要。CDN 证书验证通过,认证的是客户端到 CDN 的这一跳,并不自动认证之后到源站的每一跳。公共访问路径和相关上游路径需要分别监控。诊断应保留目标及观察时间,区分证书故障与 HTTP 故障,同时避免把凭据和私有请求数据写进输出。 快速验证 保存为 health_probe.go,使用 Go 1.23 或更新版本运行 go run health_probe.go。本地 TLS 1.3 服务提供四种应用响应,另一个明文服务充当重定向目标。客户端使用 httptest 提供的配置来信任测试服务器证书,不改变全局信任库,也不访问任何公共入口。 ...

十一月 9, 2026 · 3 分钟 · XB Field Notes
ClientHello 分支选择 alpha 或 beta 证书,之后才是独立 HTTP 层。

SNI 根据请求主机名选择 TLS 证书

服务器名称指示 SNI 在 TLS 服务器选择握手参数和证书时,提供客户端希望访问的主机名。本文讨论的是证书认证且没有 early data 的路径,因此选择发生在普通 HTTP 字节发送之前,不是网络上任何字节都还没出现的时候。SNI 只是帮助选择,客户端仍然必须验证返回的证书身份。 同一个地址上的不同决策 客户端把 server_name 放在 ClientHello 中,托管多个名称的服务器可以据此选择证书或安全配置。TLS 1.3 在 ServerHello 之后加密证书消息,所以“选证书和传证书都在加密开始之前完成”不是准确描述。应区分先收到选择提示,和随后如何保护证书及其他握手消息。 SNI 中的 DNS 主机名不等于建立连接时使用的目标 IP,填写名称也不要求实验域名真的经过公共 DNS 解析。RFC 6066 不允许在 HostName 字段直接放入 IPv4、IPv6 字面地址,并规定 DNS 主机名比较不区分大小写。客户端直接按 IP 访问时,可以不发送 DNS SNI,转而验证对应的 IP 身份。 ...

十一月 8, 2026 · 4 分钟 · XB Field Notes
URL 内高亮查询令牌分支传播到日志、复制链接与地址记录;下方独立 URL 和请求头色条表示字段分离。

让可复用访问令牌远离请求地址

可复用的持有者访问令牌把约定访问能力交给持有它的人;发送者约束令牌还要求提供与发送者绑定的证明。把令牌放进地址,会让这项能力进入平时就会复制地址的系统,例如访问日志、监控记录、浏览器导航历史、书签和分享链接。具体暴露范围取决于请求及策略,但查询参数的名称看起来像秘密,并不会自动让它受到保护。传输位置本身就是凭据披露边界的一部分,应当结合它会经过哪些记录和分享环节判断。 对于 OAuth 持有者令牌请求,应采用 Authorization 请求头;RFC 9700 现已禁止客户端按照 RFC 6750 的查询方式,在 URI 查询参数中传递访问令牌。HTTPS 仍然不可缺少,因为把值从查询移动到请求头并不会加密它;TLS 保护的是到终止位置为止的传输数据。传输结束后,应用和中间组件仍然能够检查字段。请求头的直接收益,是让令牌离开请求地址,而不是保证它永远不会被日志或诊断工具捕获。不能把两种不同的保护作用混为一谈。 分清减少暴露与绝对保密 某些访问日志只记录方法、请求目标和状态。在这种格式里,请求头令牌不会出现在目标字段中,而查询令牌会出现。但其他系统可能采集请求头,因此授权字段仍需在日志、链路和错误报告配置中被明确处理。应验证真实管线究竟保存了什么,不能假定请求头可以绕过全部日志层。检查时使用无效的合成值即可,不应为了演示而把真正凭据再次写入更多记录。 引用来源策略能够减少源地址向后续目标披露,却不会删除已经发给目的地的查询内容,也不会抹去浏览器记录或改写已复制的链接。同样,禁止缓存响应并不是要求清除全部访问日志和导航历史的通用指令。把令牌移到片段,会改变 HTTP 请求发送哪些内容,但值仍可被页面脚本和浏览器地址状态访问,也不是适合所有场景的凭据存储方案。 坏掉的样子 团队可能把接口认证从查询参数改成请求头,却让旧的带令牌地址继续留在报告和共享书签中。传输方式改变并不会撤销此前暴露,需要调查受影响的副本,并在系统支持时使已经暴露的可复用凭据失效。另一种误判是把有效期很短的链接称为泄露也无所谓;在有效期间,任何取得这项能力的人仍然可能使用它。缩短时间窗口只能减少风险,不能取消披露的后果。 当实际客户端流程需要时,签名地址可以是有意设计的、范围很窄的资源访问方式,但允许访问的资源、操作、期限和处理规则都应明确审阅。到期失效与只能使用一次并不相同,要阻止到期之前重复使用,需要真正强制执行消费状态的机制。简单的回调接口或浏览器导航,也不会自动构成把无限制可复用接口凭据放进查询的理由,应根据真实客户端选择合适的认证流程。 请求头位置减少 URL 暴露,但请求头仍须保护。签名资源 URL 是独立限定的设计,不是 OAuth 例外。运行与技术检查仍受阻。 快速验证 保存代码为 token-placement.py,执行 python3 token-placement.py --serve,打开打印出的页面。原文报告使用 Python 3.14.4 和 Chrome 152,本轮源码复核没有复现。显式 Python 检查在优化模式下仍会执行;全部演示值都是明确无效的固定标记,不能替换为真实凭据。回环服务只是观察实验,不是认证服务;它记录请求目标以及示例授权头是否存在,不记录该请求头的值。Python 已禁用代理发现并拒绝重定向,浏览器代理策略仍须单独控制。服务模式默认保留 120 秒,--serve-seconds 接受 1–300;控制键加 C 也会请求清理。只有单独的清理结果确认服务线程停止且监听关闭;有时限的 join 未能确认结束就会报错。临时事件只保存在进程内存,前面的检查通过不证明清理已经完成。 ...

十一月 1, 2026 · 3 分钟 · XB Field Notes
两种可选的 Cookie 主机集合:省略 Domain 仅匹配 app.example.com,但不隔离端口;Domain=example.com 扩大到父域与 shop 等匹配子域。

Cookie 作用域让会话留在该待的地方

Cookie 的主机与路径作用域,决定哪些目的地有资格收到它的名称和值。缩小这个集合有助于减少暴露,但作用域不是授权,而且 Cookie 并不采用浏览器完整的源边界。本文重点讨论 Domain 与 Path;传输方式、请求语境和浏览器策略还会增加其他限制,不能只凭一个属性就推断最终发送结果。 这里只展示发送资格,不代表授权或最终一定发送。公共后缀及其他 Cookie 规则仍适用;Path 和端口不隔离不可信应用。图示不宣称浏览器一致性验证。 Domain 选择主机,不隔离端口 省略 Domain 会建立 host-only Cookie。从 app.example.com 设置的 Cookie 只匹配这个主机,不匹配 shop.example.com。加入 Domain=example.com,则允许父域与匹配的子域收到它,但仍受其他 Cookie 条件约束。属性处理会忽略一个前导点,它并不是另一种通配符功能。只有那些更宽的接收方确实需要这个值,才应扩大作用域。 ...

十月 25, 2026 · 2 分钟 · XB Field Notes
配置信任锚通向父区签名的 DS 记录集。DS 摘要标识子区 DNSKEY,再沿子区密钥关系验证签名记录集。父区不直接签署子区密钥。

DNSSEC 验证签名数据链,不只检查签名是否存在

DNSSEC 为 DNS 数据增加来源认证和完整性检查。RRSIG 对记录集签名,DNSKEY 发布验证密钥,父区 DS 则通过摘要标识一个子区 DNSKEY。父区签名的是自己的 DS 记录集,不是简单直接签署子区公钥。验证从配置的信任锚出发,沿这些关系抵达所请求数据。理解链中每类记录的责任,才能准确定位发布与验证之间的差异。 摘要边标识子区密钥;签名边验证记录集。来源使用本地 lab.test 锚,不测试公共根或注册商路径。经认证的无 DS 委派属于不安全状态,与损坏的安全链不同。制图未执行 DNSSEC 实验,也未创建密钥。 坏掉的样子 如果委派没有建立预期信任路径,仅发布签名并不足够。缺失 DS 可能让子区处于不安全状态,错误 DS 则可能让原本应安全的子区验证失败;这是不同状态。已经安全证明不存在 DS,并不要求把每个未签名或独立签名的子区回答都当成无效数据拒绝。不能把“没有形成认证链”和“已有安全链却证明失败”混成一种结果。 ...

十月 21, 2026 · 3 分钟 · XB Field Notes