Skip to content

js: Protect registry tables and namespaces from endpoint writes - #8359

Open
Eddy Ashton (eddyashton) wants to merge 3 commits into
mainfrom
agents/js-registry-protect-own-tables
Open

js: Protect registry tables and namespaces from endpoint writes#8359
Eddy Ashton (eddyashton) wants to merge 3 commits into
mainfrom
agents/js-registry-protect-own-tables

Conversation

@eddyashton

Copy link
Copy Markdown
Member

Summary

JS endpoints could directly modify registry-managed code and endpoint metadata outside the installation path. This makes those tables and the configured registry namespace read-only by default.

  • Preserve stricter application restrictions and protect reassigned registry tables.
  • Add protect_registry_tables = true to the existing namespace restriction setter. Passing false opts out; ({}, false) restores platform-only permissions.
  • Clear cached interpreters when policy changes so retained KV handles cannot preserve stale permissions. The cache interface provides a non-pure default that reports unsupported invalidation.
  • Add unit and e2e coverage for defaults, opt-outs, namespace boundaries, and policy changes with interpreter reuse.

Validation

  • Built js_test, programmability, and js_generic.
  • Passed js_test and programmability_and_jwt with CR_FILTER=programmability.
  • Reproduced the namespace-write and stale-handle regressions before their fixes.
  • Passed all checks in scripts/ci-checks.sh.

BaseDynamicJSEndpointRegistry stores endpoint metadata, module source,
compiled QuickJS bytecode and runtime options in KV tables under a
configurable prefix (default public:custom_endpoints). check_kv_map_access
treats these as ordinary public app tables, so any JS endpoint executing in
a read-write transaction could overwrite them, including planting bytecode
consumed by KvBytecodeModuleLoader and rewriting endpoint auth policies.

The registry now always intersects the app-provided namespace restriction
with a built-in one that makes its own tables READ_ONLY. Table names are
resolved at request time via a virtual get_registry_managed_tables(), so
subclasses which reassign the map names (GovernanceDrivenJSRegistry) or add
tables (DynamicJSEndpointRegistry audit tables) are covered without
reserving unrelated namespaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reserve the registry namespace by default and allow applications to opt out through the existing namespace restriction setter. Clear cached interpreters when policy changes so retained KV handles cannot preserve stale permissions.

Cover defaults, opt-outs, namespace boundaries and policy changes with unit and end-to-end tests. Keep cache extensions source-compatible and document the behavior in the next release.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 11, 2026 16:29
@eddyashton
Eddy Ashton (eddyashton) requested a review from a team as a code owner September 11, 2026 16:29
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical namespace-protection issue remains unresolved, along with requested regression coverage for reassigned registry tables.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Protects JS registry-managed tables and namespaces from endpoint writes by default, with opt-out support and interpreter-cache invalidation when policies change.

Changes:

  • Adds registry table and namespace protections.
  • Clears cached interpreters after policy changes.
  • Adds unit/e2e coverage and updates release metadata.
File summaries
File Description
tests/programmability.py Tests namespace protection and boundaries.
src/js/test/js.cpp Tests restrictions, opt-outs, and cache behavior.
src/js/registry.cpp Applies effective restrictions and cache invalidation.
src/js/interpreter_cache.h Implements interpreter-cache clearing.
python/pyproject.toml Updates the package version.
include/ccf/js/registry.h Defines protection configuration and managed tables.
include/ccf/js/interpreter_cache_interface.h Adds cache-invalidation interface support.
CMakeLists.txt Updates JS test sources.
CHANGELOG.md Documents the behavior change.
Review details

Suppressed comments (2)

include/ccf/js/registry.h:187

  • Please add a regression test for the reassigned-table path represented by GovernanceDrivenJSRegistry. The new tests only instantiate DynamicJSEndpointRegistry with tables under public:custom_endpoints.*, so they do not exercise this override with the public:ccf.gov.* maps assigned by include/ccf/js/samples/governance_driven_registry.h:28-33; a regression here could leave the shipped registry tables writable without failing the added coverage.
    std::set<std::string> get_registry_managed_tables() const override
    {
      auto tables =
        BaseDynamicJSEndpointRegistry::get_registry_managed_tables();
      tables.insert(recent_actions_map);

src/js/test/js.cpp:426

  • This loop only tests names in public:custom_endpoints.*, where the prefix match alone protects them. It does not exercise the purpose of the new exact-table set for a subclass that reassigns a registry member to an application table outside the prefix; add a fixture with such a reassignment. Without this, exact-table protection could regress while all current tests pass.
  for (const auto* suffix :
       {"modules",
        "modules_quickjs_bytecode",
        "modules_quickjs_version",
        "metadata",
        "interpreter_flush",
        "runtime_options",
        "recent_actions",
        "audit.input",
        "audit.info",
        "my_table"})
  {
    check_write(
      fmt::format("public:custom_endpoints.{}", suffix),
      !protect_registry_tables && read_write);
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/js/registry.cpp
BaseDynamicJSEndpointRegistry::BaseDynamicJSEndpointRegistry(
ccf::AbstractNodeContext& context, const std::string& kv_prefix) :
ccf::UserEndpointRegistry(context),
registry_managed_prefix(fmt::format("{}.", kv_prefix)),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants