Skip to content

Cross-module local-store namespace collision when module names are identical (no uniqueness check at boot) #61

Description

@lgahdl

Summary

Local-store namespace isolation is derived entirely from keccak256(module_name) — there is no check anywhere in the boot sequence that module names configured in engine.toml's [[modules]] list are actually unique. Two modules configured with the same [module].name share one local-store namespace outright: same prefix, same keys, same quota counter.

Affected code

  • crates/nexum-runtime/src/host/local_store_redb.rsModuleStore::module() / build_key()
  • crates/nexum-runtime/src/supervisor.rsboot() (no uniqueness check across engine_cfg.modules)

Finding

ModuleStore::module(namespace) derives prefix = keccak256(namespace.as_bytes()), and build_key() always prepends the caller's own fixed prefix — there's no guest-suppliable namespace parameter, so a module can't forge its way into another module's range from inside the WIT surface (this part of the isolation design holds).

But Supervisor::boot() never checks that [module].name is unique across the configured module list before booting. If two entries share a name — accidentally, via copy-paste, or a deliberately malicious module manifest naming itself identically to a legitimate one already running — both get the identical keccak256 prefix and therefore full read/write/quota access to what the operator believed was an isolated store.

This is worse than the capability-escape issue in one respect: it's completely unlogged. No warning, no error — both modules boot and run normally, silently sharing state.

Impact

A malicious or careless module author can collide with another module's local-store namespace simply by naming their module identically in module.toml — no code-level exploit needed, just a naming collision that nothing detects.

Suggested fix

Validate [module].name uniqueness across all configured modules at boot (Supervisor::boot), and fail loudly on a collision.

Found via

Internal red-team exercise (malicious module developer persona). Verified dynamically: two fixture modules configured with the same name in their manifests, booted together via Supervisor::boot, successfully read back a value one of them wrote via local-store using the other module's store handle. A control test confirms distinct names stay fully isolated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions