跳至正文
安全

保护一个租户的到底是什么,直说。

没有我们并不持有的认证,也没有用形容词冒充控制。这里写的是什么被强制执行、在哪里执行,以及缺了什么。

十六个工作区,彼此之间空无一物一个四乘四的工作区单元格阵列,每格内含操作员、密钥、配置版本和审计日志。格与格之间的间隔画成实线,图中任何地方都没有路径跨越它们。其中一格以三倍尺寸展开:一个限定于该工作区的存储库、一个 API 密钥和一个 webhook 密钥(各自带有密钥 id),以及一条由数据库触发器强制保证的只可追加的审计日志。工作区隔离是结构性的01020304050607080910111213141516单个工作区workspace_id数据层的每个查询都需要一个api_key(kid)在重叠窗口内轮换webhook_secret(kid)以同样方式轮换audit_log仅可追加 —— 由数据库触发器强制执行没有任何路径跨越间隙 —— 缺失本身就是重点数据层的每个查询都需要一个工作区,没有任何调用点可以省略它。
十六个工作区。任何两个格子之间都没有连线,因为它们之间根本写不进任何东西。
多租户

隔离是结构性的,不是靠人盯的。

对工作区所属数据的每一次查询,谓词里都带着工作区 id。不存在一条无作用域的访问路径可供人忘记使用。

这个设计消除的是最普通的那种故障:一个本来正确的系统里,某个赶工写出的新接口漏掉了一个 WHERE 子句。如果数据层根本不回答没有指名工作区的问题,那个接口就不会编译成一次泄漏——它根本编译不过。

对抗性泄漏测试把这个性质钉死,而不是信任约定,因为一条约定的可靠程度,只等于最后一个读过那个文件的人。

internal/tenancy · migrations/0002

记录

审计日志的仅可追加是在数据库里,不是在应用里。

任何更新或删除都会触发报错。一个决定不改写历史的应用是政策;一个拒绝改写历史的数据库才是控制。

这正是审计师会问、而供应商通常答不上来的差别。如果阻止追溯修改的唯一理由是没有代码路径会那么做,那么一段支持脚本、一次数据库迁移,或者一位手握控制台的操作员就能做到,而日志不会知道。

internal/tenancy · migrations/0002 audit_log_immutable()

凭据

密钥轮换不需要停机。登录可抵御钓鱼。

需要维护窗口的轮换,就是不会发生的轮换。

两个空间,以及其间的平面两个等轴测体块,分别立在两块彼此分开的网格地面上,其间有一片竖直平面以侧视方式呈现。左侧体块属于运营方,分为四个隔舱:Stripe、NOWPayments、银行和钱包。右侧体块属于平台,分为账本、运行批次、提取申请和发件箱。四条线穿过中间的平面,标注就在平面之上:事件、指令、回执和命令。没有任何一条线承载资金,而右侧体块也没有任何一个隔舱能够持有余额。两个空间 · 一个平面 · 没有门STRIPE账本NOWPAYMENTS运行银行提取钱包发件箱运营方XEX托管边界事件指令回执命令没有任何体量跨越资金停留在运营方的隔间中,绝不会复制到我们这边。有四样东西跨越该平面。它们都不是余额,也都不是密钥。
两个体块,中间一个平面。平面上没有门。
  1. 01

    带重叠有效期的密钥 id

    每个工作区的 API 密钥和 webhook 签名密钥都带一个密钥 id。轮换期间两者都有效,所以您按自己的节奏切换,之后再吊销旧的。

    internal/tenancy · internal/outbox/sign.go

  2. 02

    会员和操作员都用通行密钥

    WebAuthn,强制要求用户验证和可发现凭据——不是可选项,因为这些令牌就挡在资金流前面。可发现登录直接从凭据解析出账户,所以不用输入任何东西,也就没有东西可被钓。

    internal/passkey

  3. 03

    验证器双因素,存储方式正确

    TOTP 密钥无法做哈希——校验要用它重新算出验证码——所以会员的密钥用 AES-256-GCM 在按用途派生的子密钥下密封保存,而不是明文存放。被接受的验证码会回写计数器,所以同一个码在有效窗口内无法重放。

    internal/membertotp · internal/secretbox

  4. 04

    能哈希就哈希,不能哈希就密封

    密码或恢复码会被哈希,因为平台永远只需要回答“是不是这个”。平台必须读回来的密钥则改为密封保存,而早于这套构造的存储正在迁移过来,而不是原样留着。

    internal/secretbox

出站

没有东西会从您配置的 URL 流出去。

webhook 目的地在构造上就是受攻击者影响的输入——那是租户敲进来的一个 URL,随后平台会从自己的网络内部去请求它。

分发器在发出请求之前先跑一遍出站守卫,而不是信任主机名;只有这个顺序才管用:在域名解析之后做的检查,或者对字符串而不是对地址做的检查,正是 DNS 重绑定专门用来穿过的那种检查。回环地址和内网目的地只在沙箱和端到端测试中被允许,而且这项放宽是一个显式开关,不是靠嗅探环境。

internal/outbox/ssrf.go · internal/outbox/dispatcher.go

托管

我们不是托管方,代码也拒绝变成托管方。

不碰会员资金。不碰私钥。租户的收款通道里不放平台的凭据。

会员付给您

每条通道都用租户自己的凭据、配置在租户自己的账户上。

internal/rails

配置会拒绝我们自己的凭据

保存与平台自有凭据相同的凭据会被拒绝。这道检查之所以存在,是因为“先用我们的顶一下”就是一家软件供应商不小心变成支付机构的方式。

internal/rails

提取只下指令,从不搬钱

一次提取会锁定确切的总账分录并发出一条指令。执行是您的,被执行的那笔钱也是您的。

internal/claims

身份

不留证件的身份核验。

服务商会话跑在您自己的账户上。返回的是一个决定和一个不可逆的句柄。

这个句柄只够执行“一人一位置”,仅此而已。它不足以还原一个身份,而这正是重点:保管护照扫描件最安全的方式就是不保管,而泄露也无法泄出从未存过的数据。

internal/verification

我们不宣称

我们不宣称的事。

如果您正在填供应商问卷,以下就是我们会不及格的那几行。在这里知道,好过到第四个月才知道。

我们没有 SOC 2 报告。未做过第三方渗透测试。我们不发布任何可用性承诺,因为这里还没有任何东西在测量它。XEX 给您的是控制手段和记录;它不给您牌照,也不是法律意见。您的薪酬计划在您所营销的国家/地区是否合法,须由您自行确认,我们的平台条款也要求您这样做。
漏洞披露
目前没有漏洞赏金计划,提交报告也不会付酬。请通过联系页面上的安全通道报告漏洞;我们会确认收到,并告诉您我们在如何处理。请不要针对真实租户的工作区做测试——领一个免费沙箱,它就是完整产品,请在那里测。

See /contact#security

下一步

把问卷发过来,或者拿个沙箱自己看。

两种都行。其中一种更快。