No, npm, “estee” is not a typo of “testem”
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
No, npm, “estee” is not a typo of “testem”
不,npm,“estee”并不是“testem”的拼写错误。
So, alright, look, here’s the thing. Storyteller has a really long npm namespace because “storyteller” is a really common word, and so “@storyteller/” was already taken (by someone who doesn’t actually seem to use it, which, if you’re reading this, I’m still interested in your namespace!). So we’re stuck with “@storyteller-platform/”, and that means our command line tool for book forced alignment is named “@storyteller-platform/align”, and so you need to run “npx @storyteller-platform/align —help”, etc, which is a handful!
好吧,听着,事情是这样的。Storyteller 的 npm 命名空间非常长,因为“storyteller”是一个非常常见的词,所以“@storyteller/”已经被占用了(而且占用者似乎并没有在使用它,如果你正在读这篇文章,我对你的命名空间依然很感兴趣!)。所以我们只能使用“@storyteller-platform/”,这意味着我们用于书籍强制对齐的命令行工具被命名为“@storyteller-platform/align”,因此你需要运行“npx @storyteller-platform/align —help”等等,这实在太麻烦了!
And so we came up with a cute nickname, “estee” (get it? Storyteller -> ST -> estee?), and we were pretty happy with it, so I went to publish a v0.0.1 to npm so I could set up Trusted Publishing with our GitLab repo and then this happened: 403 Forbidden - PUT https://registry.npmjs.org/estee - Package name too similar to existing packages espree, testem… What? Too similar to… “testem”? I mean I can almost understand “espree”. I don’t actually see how someone could accidentally typo “espree” as “estee”, but at least they’re similarly shaped. But “testem”? No chance, that is not a typo candidate for “estee”.
于是我们想出了一个可爱的昵称“estee”(明白了吗?Storyteller -> ST -> estee?),我们对此很满意,所以我准备发布一个 v0.0.1 版本到 npm,以便在我们的 GitLab 仓库中设置可信发布(Trusted Publishing),结果发生了这种情况:403 Forbidden - PUT https://registry.npmjs.org/estee - 包名与现有包 espree、testem 太相似……什么?与“testem”太相似?我勉强能理解“espree”。我实在看不出有人会把“espree”误拼成“estee”,但至少它们的形状相似。但“testem”呢?绝无可能,那根本不是“estee”的拼写错误候选词。
I mean it’s pretty clear what’s happening here. Npm is using a Levenshtein distance calculation and banning any packages that have a Levenshtein distance of 2 or less from existing popular package names. Levenshtein distance counts the number of “edits” needed to get from one string to another, where an edit is a single character insertion, replacement, or deletion. So to get from “estee” to “testem”, we have to make the following edits: teste -> testem (see? Levenshtein distance of 2). We used this for the first several iterations of the Storyteller algorithm, which needs to find where a given sentence lives in a transcription of an audiobook.
很明显,这里发生了什么。Npm 正在使用莱文斯坦距离(Levenshtein distance)计算,并禁止任何与现有流行包名莱文斯坦距离为 2 或更小的包。莱文斯坦距离计算从一个字符串转换到另一个字符串所需的“编辑”次数,其中编辑是指单个字符的插入、替换或删除。所以要从“estee”变成“testem”,我们需要进行以下编辑:teste -> testem(看到了吗?莱文斯坦距离为 2)。我们在 Storyteller 算法的最初几次迭代中也使用了这个方法,该算法需要找到给定句子在有声读物转录本中的位置。
Here’s the thing: Levenshtein doesn’t scale down very well. You can’t just set a single threshold for all input lengths, because as the length of the string approaches the threshold, the proportion of the set of valid strings covered by the threshold explodes.
问题在于:莱文斯坦距离在短字符串上的表现并不好。你不能为所有输入长度设置单一的阈值,因为当字符串长度接近阈值时,被阈值覆盖的有效字符串集合比例会呈爆炸式增长。
Here’s what I mean. Say you are concerned with all npm package names with length 4. Npm allows the characters a-z, plus the symbols “.”, “-”, and “_”, so there are 29 possible characters in our alphabet. For a 4-character string, that gives us 29^4 possible names, or 707,281 possible names. But what happens if you don’t allow any names that have a Levenshtein distance of 2 from any existing names? How many packages can exist before you have to start rejecting all new package names? This is a little hard to answer completely (and we’re ignoring the fact that, because Levenshtein distance allows pure insertions and deletions, this also affects 2-, 3-, 5-, and 6-character names), but we can set an upper bound: 29^2, or 841.
我的意思是:假设你关注所有长度为 4 的 npm 包名。Npm 允许使用 a-z 字符,加上符号“.”、“-”、“_”,所以我们的字符集有 29 种可能。对于 4 字符的字符串,这给了我们 29^4 种可能的名称,即 707,281 个名称。但如果你不允许任何与现有名称莱文斯坦距离为 2 的名称,会发生什么?在必须拒绝所有新包名之前,能存在多少个包?这很难完全回答(我们忽略了莱文斯坦距离允许纯插入和删除这一事实,这也会影响 2、3、5 和 6 字符的名称),但我们可以设定一个上限:29^2,即 841 个。
This is because, since Levenshtein distance allows a replace edit, you can get from a string like “abaa” to any string starting with “ab”. So once you have all 841 strings starting with every combination of the first two characters, that’s it, you’re done, no more names. That’s 0.12% of the total name space actually available to choose from.
这是因为,由于莱文斯坦距离允许替换编辑,你可以从像“abaa”这样的字符串转换到任何以“ab”开头的字符串。所以一旦你拥有了所有以头两个字符的每种组合开头的 841 个字符串,就结束了,没有更多名称可用了。这仅占总命名空间中实际可选名称的 0.12%。
This is especially apparent when considering our backup name, “hoid” (get it? Hoid? World-hopping Storyteller?). Here are the popular packages that npm thinks “hoid” might be typosquatting on: hoek, hono, uid, ulid, cuid, uuid, joig, opd, poiz, od. Two things to notice about this list: It’s much longer than the first list. This is because of the string-length — 4 is so close to 2 that you can reach way more usable words from a given length-4 string with a distance-2 Levenshtein threshold. None of these look like actual typo candidates for “hoid”. They’re all at least 50% different from “hoid”! “gopd”? “gopd”!?
当我们考虑我们的备用名称“hoid”(明白了吗?Hoid?世界旅行者 Storyteller?)时,这一点尤为明显。以下是 npm 认为“hoid”可能进行拼写劫持的流行包:hoek、hono、uid、ulid、cuid、uuid、joig、opd、poiz、od。关于这个列表,有两点需要注意:它比第一个列表长得多。这是因为字符串长度——4 与 2 非常接近,以至于在距离为 2 的莱文斯坦阈值下,你可以从给定的 4 字符字符串中触及更多可用词汇。这些看起来都不像是“hoid”的实际拼写错误候选词。它们与“hoid”至少有 50% 的不同!“gopd”?“gopd”!?
Also, this just isn’t a very good rule. Seriously, this rule would have banned the project “preact” from being created because “react” already existed. It would have banned “estree” (which I assume only didn’t trigger on my “estee” attempt because it’s not sufficiently “popular”) from being created because “espree” already existed. I could go on and on. I understand that npm is under a lot of pressure to try to prevent this flavor of typosquatting malware distribution, and I know that there probably needs to be some amount of automation involved here, but surely there should be some sort of manual override?
此外,这根本不是一个好的规则。说真的,这个规则会禁止“preact”项目的创建,因为“react”已经存在了。它会禁止“estree”(我猜它没有在我的“estee”尝试中触发,只是因为它不够“流行”)的创建,因为“espree”已经存在了。我可以一直列举下去。我理解 npm 在防止这种类型的拼写劫持恶意软件分发方面承受着巨大压力,我也知道这里可能需要一定程度的自动化,但肯定应该有某种人工干预机制吧?
Actually that’s a good point. In fact, when I tried to first publish Storyteller’s “@storyteller-platform/audiobook” package, I ran afoul of npm’s other rule about new package names, which is that they can’t, I dunno, use the word “audiobook”? That name is not allowed. Cool, thanks. I think what actually happened here is that people were publishing literal copyrighted audiobooks on npm. Anyway, I emailed npm support that time, and they immediately disabled the block for “audiobook” and allowed me to publish it. This was actually a wonderful customer support experience, so much so that when I ran into my issue above, the first thing I tried was to reach out to npm again.
事实上,这是一个很好的观点。事实上,当我第一次尝试发布 Storyteller 的“@storyteller-platform/audiobook”包时,我触犯了 npm 关于新包名的另一条规则,即它们不能,我不知道,使用“audiobook”这个词?该名称不被允许。酷,谢谢。我想实际发生的情况是,人们在 npm 上发布了受版权保护的有声读物。无论如何,那次我给 npm 支持团队发了邮件,他们立即禁用了对“audiobook”的屏蔽,并允许我发布它。这实际上是一次非常棒的客户支持体验,以至于当我遇到上述问题时,我尝试的第一件事就是再次联系 npm。
Only, this time they weren’t so helpful (I’m sure they’re getting deluged with LLM-generated support requests, just like everyone else, and they were still perfectly pleasant): The error you received is a typosquat functionality that will prevent security vulnerabilities. For more information on typosquat please refer to: https://blog.npmjs.org/post/168978377570/new-package-moniker-rules. We are unable to get around the automated typosquat block.
只是,这一次他们没那么有帮助(我敢肯定他们像其他人一样,正被 LLM 生成的支持请求淹没,而且他们依然非常有礼貌):你收到的错误是防止安全漏洞的拼写劫持功能。有关拼写劫持的更多信息,请参考:https://blog.npmjs.org/post/168978377570/new-package-moniker-rules。我们无法绕过自动化的拼写劫持屏蔽。
Only, here’s the thing, that blog post doesn’t say what the support person says it does. Instead, it’s about how npm rejects any package names that are the same as existing package names except for punctuation. That’s not what we’re experiencing at all! Also, I suspect that someone at npm/GitHub can get around this typosquat block. Plenty of pre-existing packages break this rule — if someone created the package for me from a backend/admin interface that didn’t enforce the block, I could use it.
但问题是,那篇博客文章并没有说支持人员所说的那样。相反,它讲的是 npm 如何拒绝任何除了标点符号外与现有包名相同的包名。这根本不是我们所经历的情况!此外,我怀疑 npm/GitHub 的某个人可以绕过这个拼写劫持屏蔽。许多预先存在的包都违反了这条规则——如果有人能通过不强制执行该屏蔽的后端/管理界面为我创建包,我就能使用它。