在 Substack 上收听这篇文章](https://loopbreaker.substack.com/p/exploring-user-privacy-in-ollama)
Ollama 是一家初创公司,提供了一个开源工具,用于在本地运行大型语言模型(LLM)。因为注重用户隐私,Ollama 已成为数百万用户在本地运行 AI 的首选工具。
奥拉玛是由Y Combinator孵化器孵化出来的——这让我感到非常惊讶。为什么Y Combinator,这家之前由山姆·阿尔特曼领导的硅谷孵化器,会投资一个专注于用户隐私的初创公司?为什么他们会投资一个让用户私下运行LLM的产品?
这些问题还没答案,但我开始了一段探索,想看看Ollama是否真的那么私密。跟我一起看看Ollama的代码、运作方式和架构。
此处省略内容
我在这里列出了我的发现。如果你想了解更多细节和强化方法,可以继续往下读。
1 - 在监控网络使用情况并检查代码后,未发现私人用户数据被发送到外部服务器的证据。然而,我发现了一些基础工作,这可能让Ollama在将来在用户不知情的情况下偷偷实现这一点,而无需用户同意。 2 - Ollama 会把用户的聊天记录保存为名为“history”的纯文本文件(.txt)。如果历史文件被删除,Ollama 会悄悄地重新生成它。
4 - 限制访问历史文件会导致Ollama拒绝加载模型并开始会话。
5 - 我找到了一种不用更新历史文件就能运行Ollama的方法,但这种方法没有在文档中提到,见下面的“加固方法”部分。 6 - Ollama 生成日志文件,包含用户与本地 API 的所有交互,包括所加载模型的名称。 7 - Ollama 会自动从远程服务器更新,也无法选择退出更新。 8 - 当你使用 Ollama 推送至现有的远程仓库时,会一起推送用户的聊天历史(据一个未验证的 Youtube 视频说法)。网络监视
最简单的验证方式是看看软件是否偷偷向外部服务器发送数据包。鉴于Ollama是开源的,大家最初的假设是“如果他们这么做,早就被人发现了”——这通常是对的。然而,我还是决定自己验证一下,而不是直接相信他们。
我想看看源代码,看看有没有什么不对劲的地方。
我从浏览Ollama的代码库开始,这个代码库主要是用Golang编写,其中一些部分使用了C++来实现用C++编写的llama.cpp部分。在熟悉代码库后,我发现Ollama实际上是一个本地的HTTP API。命令行工具通过调用本地的Ollama API,后者处理模型操作和数据流。
_Host
函数用于创建大多数 API 调用所需的 URL 实例。它将 “127.0.0.1” 硬编码为 URL 的默认主机——一切正常到目前为止。
// config.go
func Host() *url.URL {
...
if err != nil {
host, port = "127.0.0.1", defaultPort
if ip := net.ParseIP(strings.Trim(hostport, "[]")); ip != nil {
host = ip.String()
} else if hostport != "" {
host = hostport
}
}
...
return &url.URL{
Scheme: scheme,
Host: net.JoinHostPort(host, port),
Path: path,
}
}
切换到全屏,退出全屏
我发现了一个在 IsNewReleaseAvailable 函数里的自动外部HTTP调用,这个函数每小时运行一次,并将用户的操作系统、机器架构、当前版本和时间戳发送给服务器。
// 检查是否有新版本可用
func 有新版本可用(ctx context.Context) (bool, UpdateResponse) {
// 省略了其他代码
query := requestURL.Query()
query.Add("os", runtime.GOOS) // 获取操作系统类型
query.Add("arch", runtime.GOARCH) // 获取系统架构
query.Add("version", version.Version) // 获取当前版本
query.Add("ts", strconv.FormatInt(time.Now().Unix(), 10)) // 获取当前时间戳
// 省略了其他代码
}
全屏模式(按一下退出)
// index.ts (electron 包装)
async function checkUpdate() {
const available = await isNewReleaseAvailable()
if (available) {
logger.info('检查更新...')
autoUpdater.checkForUpdates()
}
}
function init() {
if (app.isPackaged) {
checkUpdate()
setInterval(() => {
checkUpdate()
}, 60 * 60 * 1000)
}
...
}
全屏显示,切出全屏
注意,Ollama 使用了 Electron 的 autoUpdater。根据 Electron 的文档:“autoUpdater 允许应用程序自动更新”(有些怪异)。我没有找到关闭自动更新的选项,这意味着用户可能在不知情或未经同意的情况下,随时可能被更新代码。
网络使用情况
我没有发现任何明显的代码块将私人用户数据发送出机器。我更进一步,验证了在使用软件时的网络使用情况。从晚上8点到凌晨3点,我一直在监控软件的网络使用情况,同时与几个大语言模型交谈了好几个小时,并记录了每一次网络交互。
ollama app.exe, 端口: 15460 -> ollama.com @ 下午7:53 重新启动
ollama app.exe, 端口: 15460 -> ollama.com @ 下午8:53 下载 5Kb, 上传 2Kb
ollama app.exe, 端口: 15460 -> ollama.com @ 下午9:53 下载 5Kb, 上传 2Kb
ollama app.exe, 端口: 15460 -> ollama.com @ 晚上10:53 下载 5Kb, 上传 2Kb
ollama app.exe, 端口: 15460 -> ollama.com @ 晚上11:53 下载 5Kb, 上传 2Kb
ollama app.exe, 端口: 15460 -> ollama.com @ 早上12:53 下载 5Kb, 上传 2Kb
ollama app.exe, 端口: 15460 -> ollama.com @ 1:53 下载 5Kb, 上传 2Kb
ollama app.exe, 端口: 15460 -> ollama.com @ 2:53 下载 5Kb, 上传 2Kb
全屏显示 退出全屏
这个模式与之前的自动更新匹配。注意包的大小,因为它们传送聊天记录到外部服务器时需要更多的带宽。看起来到目前为止很有希望,但仍有很多路可以走。
日志档案
默认情况下,Ollama 会生成详细的日志文件。这让我觉得很奇怪——为什么这么注重用户隐私的公司还会默认保留这么详细的本地日志呢?
默认情况下在用户的设备上记录详细的日志有什么用,如果这些日志不会传出去呢?
这些日志文件包含系统相关信息,但据我了解,它们不包含推断历史。然而,它们包含了用户与本地API接口进行的每一次交互,以及他们加载的模型的名称。
[GIN] [REDACTED FOR PRIVACY] | 200 | 184.3285ms | 127.0.0.1 | POST "/api/chat"
[GIN] [REDACTED FOR PRIVACY] | 200 | 116.5207ms | 127.0.0.1 | POST "/api/chat"
llama_model_loader: 加载了含有36个键值对和197个张量的元数据
C:\\Users\\T\\.ollama\\models\\blobs\\sha256-(GGUF V3 最新版本)
llama_model_loader: - kv 0: general.architecture (字符串) = phi3
llama_model_loader: - kv 1: general.type (字符串) = 模型
llama_model_loader: - kv 2: general.name (字符串) = Phi 4
全屏;退出全屏
在 Windows 上,你可以通过右键点击 Ollama 的托盘图标,然后选择“查看日志”来查看日志。在 Linux 上,请到以下路径查看。
Windows:
在 Windows 系统中,路径为:%appdata%/Local/Ollama/
Linux:
在 Linux 系统中,路径为:~/.ollama/logs
全屏显示 退出全屏
历史记录
最让人觉得毛骨悚然的是Ollama的历史文件。
Ollama 将用户的所有聊天记录保存为纯文本,并存储在一个名为“history”的文件中。为什么这么做?,我不太清楚原因,但有一些猜测。
我简直不敢相信,当我发现文件里竟然保存了所有聊天记录,而且是以纯文本形式,任何人都可以看得到。这时,我脑海里不断冒出疑问——虽然我知道有些历史记录需要保存在内存里而不是文件里,为什么要把这些记录存成纯文本?为什么会有这样一个文件啊?为什么在Ollama的文档中没有提到这些内容?还有,为什么不让访问历史文件的时候,Ollama就不运行了?
此外,他们可以轻松地对文件进行加密——然后让软件加载并正常使用其中的内容。它不需要以明文形式存储。更好的办法是根本不使用历史文件,而是依赖于会话期间内存中的历史记录。
关于Ollama的历史记录的几点说明:
-
在会话期间,Ollama 会创建一个“history.tmp”文件,其中保存当前会话的历史记录,以纯文本形式。会话结束后,它会将临时缓冲合并到主历史文件中,并清除临时文件。
-
历史文件有一个固定的大小限制,当一个会话结束后,如果历史文件超出了这个大小限制,它会移除缓冲区中的最早部分,直到文件大小在限制范围内,移除旧的历史部分以确保文件符合限制。
-
如果历史文件被删除了,Ollama 会悄悄地重新创建文件。如果在会话过程中历史文件被删除,Ollama 会悄悄地重新创建文件,然后将临时文件缓冲区的内容复制到文件中。
将历史文件设为只读会导致Ollama无法加载模型或开启会话。
目前,据我所知,Ollama 不会将历史文件发送到外部服务器,但这个信息可能仍然不准确。考虑到这样的文件存在以及用户无法选择退出的自动更新,我不禁想到,将来可能通过静默更新来改变这种做法。
以防万一,我搜集了一些加强隐私的方法,以确保Ollama真正私密。
硬化的办法 1
尽可能简单地——通过Windows防火墙阻止Ollama的网络访问。在Linux系统中,你可以用UFW来做到这一点。
方法2
在运行 Ollama 时,将 OLLAMA_NOHISTORY=1 设置为环境变量。
我在查看Ollama的源代码时发现了这种方法,并且验证了它是有效的。虽然这种方法并未在文档中提及(我想知道为什么),但它可以防止写入历史文件。请注意,如果Ollama有网络访问权限,这种方法的效果可能会受到影响。我建议将这种方法与方法#1结合起来以确保隐私。请注意,“查看日志”下的日志文件仍然会被写入和更新,不会受到影响。
Linux加固方法3
一种防止 Ollama 在 Linux 上写入历史文件的方法可以在这里找到 here,引用自 Github 用户 “glassbell” 的说法。
这个解决方法对我有效(删除了包含所有敏感内容的历史文件后):
$ touch history $ touch history.tmp \# chattr +i history \# chattr +i history.tmp
(是的,它还会尝试写入第二个文件)
(另外,我不清楚它是如何做到的,但如果未设置该属性,ollama 即使权限被移除也能重建文件。真奇怪)
我在 Windows 上无法实现 chattr +i 的相同效果。将历史文件设为只读会阻止 Ollama 加载模型或启动会话。如果您知道 Windows 上的其他解决办法,请告诉我!
共同学习,写下你的评论
评论加载中...
作者其他优质文章