⚠️ErisPulse Security Advisory EPSA-2026-001 #402
Replies: 1 comment
修复后,数字字段的行为说明 / Behavior of Numeric Fields After Fix以下分三种情况说明修复后的实际行为: ① 已存在的 list → 数字仍作为索引(行为不变)① Existing list → Numeric still treated as index (unchanged)storage.set("data", {"items": ["a", "b", "c"]})
storage.set("data.items.1", "UP")
# Result / 结果: {"items": ["a", "UP", "c"]} ← Still list index / 仍然是 list 索引只要容器本身已经是 list(由用户显式创建),数字段照样作为索引使用。唯一新增的是 10000 的安全上限( As long as the container is already a list (explicitly created by the user), numeric segments continue to be treated as indices. The only new addition is the 10000 safety upper bound ( ② 首次创建的中间层 → 数字变为字典的字符串键(唯一的行为变化)② Newly created intermediate layers → Numeric becomes string key in dict (the only behavior change)storage.set("QvQChat.groups.871684833", {"ai": True})
# Before / 修复前: groups = [] → treats 871684833 as index → 💥 OOM
# After / 修复后: groups = {"871684833": {"ai": True}} ← String key / 字符串键storage.set("a.b.123.c", "val")
# After / 修复后: {"b": {"123": {"c": "val"}}} ← "123" is dict key, not index
# "123" 是字典键,不是索引③ 隐式自动创建 list 的场景(不再支持)③ Implicit auto-creation of lists (no longer supported)storage.set("mylist.0", "first")
# Before / 修复前: {"mylist": [None, ..., "first"]} ← Auto-creates list (dangerous & ambiguous)
# 自动建 list(危险且有歧义)
# After / 修复后: {"mylist": {"0": "first"}} ← Creates dict, "0" is string key
# 建成 dict,"0" 是字符串键这是本次修复中唯一的语义变化:修复前,系统会根据下一段是否全是数字来「猜测」容器类型;修复后,所有新创建的中间层统一使用 dict。 This is the only semantic change in this fix: Previously, the system would "guess" the container type based on whether the next segment was all digits; after the fix, all newly created intermediate layers uniformly use dict. 为什么这个变化是合理的? / Why This Change Is Justified1. 原逻辑本身就是 Bug 的根源 1. The original logic is the root cause of the bug 那个「自动猜成 list」的判断根本没有可靠的语义依据: The "auto-guess as list" logic has no reliable semantic basis:
2. 真正需要 list 的用户应该显式创建 2. Users who truly need a list should create it explicitly # ✅ Correct / 正确做法
storage.set("mylist", ["first"])
storage.set("mylist.0", "updated") # Now works as index / 此时按索引工作而不是依赖点路径隐式创建,这种隐式行为既不直观也不安全。 Rather than relying on implicit creation via dotted paths, which is neither intuitive nor safe. 3. get/set 行为在修复后更为一致 3. get/set behavior is now more consistent 关键事实—— Key fact — # 假设 "mylist" 不存在 / Assume "mylist" does not exist
storage.get("mylist.0") # 返回 None,不会创建任何东西 / Returns None, creates nothing修复前, Before the fix, 总结 / Summary
唯一需要注意的迁移场景:如果你的代码依赖「通过点路径首次创建时自动生成 list」的隐式行为,请改为显式 The only migration concern: If your code relies on the implicit behavior of "auto-creating a list via dotted path on first write", please change to explicit |
Uh oh!
There was an error while loading. Please reload this page.
ErisPulse Security Advisory EPSA-2026-001
storage.set() Triggers OOM Kill When Nested Key Contains Large Numeric Field
storage.set() 嵌套键包含大数字段时触发 OOM Kill
The ErisPulse storage module
Core.storage's_set_nested_value()function misinterprets numeric segments in nested key paths (e.g.,mymodule.groups.871684833) as list indices when processing dot-separated keys. When the numeric value is large, a single call can allocate several gigabytes of memory, causing the process to be terminated by the OOM Killer (exit code -9).ErisPulse 存储模块
Core.storage的_set_nested_value()在处理点分隔的嵌套键路径(如mymodule.groups.871684833)时,会将纯数字段误判为 list index 并执行list.extend([None] * N)。当数字较大时,单次调用即可分配数 GB 内存,导致进程被 OOM Killer 终止(exit code -9)。This issue can be triggered by a single
storage.set()call — no loop, no recursion, no special privileges required.此问题可由单次
storage.set()调用触发——无需循环、无需递归、无需特殊权限。Affected Versions / 受影响版本
< 2.5.5>= 2.5.5Trigger Condition / 触发条件
Any
sdk.storage.set()call where the dot-separated key path contains a numeric segment ≥ 10,000:任何
sdk.storage.set()调用,当点分隔的键路径中包含 ≥ 10000 的纯数字段时:Internal call chain / 内部调用链:
Mitigation / Temporary Workarounds / 临时缓解方案
Before upgrading to 2.5.5, module developers are advised to use one of the following alternative approaches:
在升级到 2.5.5 之前,建议所有模块开发者采用以下替代方案:
✅ Option A — Use colon as separator (recommended)
✅ 方案 A — 使用冒号作为分隔符(推荐)
✅ Option B — Store ID inside the value
✅ 方案 B — 将 ID 存入 value 中
❌ Avoid the following pattern
❌ 避免以下写法
Root Cause Analysis / 根因分析
StorageManager._set_nested_value()contains two defects:StorageManager._set_nested_value()中存在两处缺陷:Defect 1 / 缺陷 1 — Line 787 creates intermediate containers based on whether
next_key.isdigit()is true:第 787 行,创建中间层时根据下一段是否
isdigit()决定容器类型:Nested key paths come from dot-separated strings (e.g.,
"mymodule.groups.871684833"). Each segment is fundamentally a dict key — the implementation should not guess the container type based on whether it looks like a number. This heuristic is fundamentally flawed — a numeric string in a dotted path is still a dictionary key, not an array index.嵌套键路径来自点分隔字符串(如
"mymodule.groups.871684833"),每一段本质上都是 dict key,不应根据是否为数字来猜测容器类型。这种启发式判断存在根本性缺陷——点分隔路径中的数字字符串仍然是字典键,而非数组索引。Defect 2 / 缺陷 2 — Lines 809-813 extend lists without any upper-bound protection:
第 809-813 行,对 list 扩展没有上限保护:
Additional risk / 附加风险 — Recursive calls at lines 801-803 and 817-818 may trigger infinite recursion when
currentis an immutable scalar type.第 801-803 行和 817-818 行的递归调用存在潜在无限递归风险,当
current为不可变标量类型时可能触发。Fix in 2.5.5 / 2.5.5 修复方案
Version 2.5.5 applies the following fixes to
_set_nested_value():2.5.5 版本将对
_set_nested_value()做以下修复:Intermediate containers always use
dict— no longer guess type based onisdigit()新建中间层始终使用
dict— 不再根据isdigit()猜测类型Add
_MAX_LIST_INDEX = 10000safety upper bound — automatically convert list to dict when exceeded添加
_MAX_LIST_INDEX = 10000安全上限 — 超出时自动将 list 转换为 dictReplace recursion with iterative implementation — eliminate infinite recursion risk
递归调用改为迭代实现 — 消除无限递归风险
Credit / 致谢
Reported by a third-party module development team during production deployment (Docker, 8GB memory limit) via TRACE-level diagnostic logs. Reported with full root-cause analysis and reproduction steps.
由第三方模块开发团队在生产环境部署(Docker,8GB 内存限制)期间通过 TRACE 级别诊断日志发现并报告。提供了完整的根因分析和复现步骤。
ErisPulse Core Team — 2026-07-10
All reactions