Skip to content

v3.4.1

Choose a tag to compare

@cldmv-bot cldmv-bot released this 12 May 02:52
· 9 commits to master since this release
v3.4.1
21df8a0

release: v3.4.1 (#85)

Slothlet v3.4.1 Changelog

Release Date: May 2026
Release Type: Patch
Branch: release/v3.4.1


Overview

Version 3.4.1 is a security-focused patch release with additional hardening in metadata handling and internal permission enforcement.

Primary changes in this release:

  • Closes a bypass where module code accessing self.slothlet.* routes could evade permission checks.
  • Hardens metadata merge paths against prototype-pollution-style keys and circular plain-object payloads.
  • Improves internal permission evaluation behavior for slothlet.* namespace traversal and enforcement paths.
  • Adds dedicated documentation and broader regression coverage around conditional permissions and internal-route gating.

No breaking changes. All v3.4.0 configuration and API usage is fully compatible.


🔐 Security Fixes

All api.slothlet.* routes are now permission-gatable

Before: Permission rules for slothlet.* paths were only enforced on a small subset of explicitly-guarded callable methods in the internal namespace. Primitive reads (e.g. self.slothlet.version), sub-namespace traversals (e.g. self.slothlet.permissions), and any route not individually wrapped were reachable from module code regardless of what permission rules were in place.

After: Every property access on self.slothlet.* — including nested paths like self.slothlet.permissions.addRule and primitive reads like self.slothlet.version — is intercepted and checked against the permission system before the value is returned to the calling module.

External access (api.slothlet.* called directly outside of any module context) is unaffected and never blocked by this layer — the guard only fires when a callerWrapper is present in the ALS context, which is only true for code running inside Slothlet module wrappers via self.

Root cause

The internal namespace returned by createSlothletNamespace() in api_builder.mjs was a plain object. enforceInternalPermission() had to be called manually inside each individual method. Non-callable properties and nested sub-namespace objects had no enforcement path at all.

What changed

createSlothletNamespace() now wraps the returned namespace object in an invariant-safe recursive Proxy:

  • The get trap resolves the full route path (e.g. "slothlet.permissions.addRule") and calls enforceInternalPermission(routePath) before returning the value.
  • Non-configurable / non-writable properties return their value directly via Reflect.get to satisfy JS proxy invariants (required to avoid TypeError when OwnershipManager iterates the namespace).
  • Sub-objects are wrapped in nested sub-proxies with the parent path prepended, so deep property chains are fully gated.
  • Redundant per-method enforceInternalPermission() calls were removed from most call sites; enforcement now lives in the recursive proxy layer and applies across get, apply, construct, and descriptor access paths.
  • Internal traversal checks now respect conditional permission semantics while still preventing unauthorized namespace probing.

Example — gating internal metadata mutations

const api = await slothlet({
	dir: "./api",
	permissions: {
		defaultPolicy: "allow",
		rules: [
			// Prevent module-b from reading or mutating slothlet internals
			{ caller: "module-b.**", target: "slothlet.**", effect: "deny" }
		]
	}
});

// From module-a (not blocked):
self.slothlet.metadata.setGlobal({ env: "production" }); // ✅ allowed

// From module-b (blocked — even for a primitive read):
self.slothlet.version; // ❌ throws PERMISSION_DENIED
self.slothlet.permissions.addRule({ ... }); // ❌ throws PERMISSION_DENIED

// External (always allowed — no callerWrapper in context):
api.slothlet.metadata.setGlobal({ env: "production" }); // ✅ allowed
api.slothlet.version; // ✅ allowed

🛡️ Metadata Hardening

Prototype-pollution key rejection in metadata merge paths

Metadata writes now reject unsafe key segments such as __proto__, prototype, and constructor in both dot-notation key usage and nested object payloads.

This applies across metadata mutation paths used by:

  • api.slothlet.metadata.setGlobal(...)
  • Module/path metadata merge helpers
  • Nested metadata payload merges

Circular plain-object rejection and safer deep merge behavior

Metadata merges now validate plain-object inputs for circular references before merge, and throw validation errors for circular payloads instead of allowing unsafe or unstable merge behavior.

Nested plain-object values are merged recursively, while scalar conflicts continue to resolve in favor of the latest write.

Bulk object support for api.slothlet.metadata.setGlobal(...)

Global metadata now supports object-form bulk input while preserving nested structure, alongside existing key/value usage.


⚙️ Permission Engine Refinements

  • PermissionManager now distinguishes silent access queries from enforcement paths more clearly (event-emitting enforcement uses a dedicated flow).
  • Internal namespace enforcement paths use the current ALS runtime context for condition-aware checks.
  • Conditional matching internals were consolidated and optimized for hot paths.

These changes do not alter public configuration shape, but tighten consistency and audit behavior under internal-route enforcement.


📚 Documentation Updates

  • Added a dedicated conditions reference: docs/PERMISSIONS-CONDITIONS.md
  • Simplified and re-focused docs/PERMISSIONS.md by moving deep condition detail into the dedicated reference.

🧪 Test Coverage

Regression and hardening coverage was expanded across permissions and metadata suites.

New permission-focused suites:

  • tests/vitests/suites/permissions/permissions-slothlet-mutation-gating.test.vitest.mjs
  • tests/vitests/suites/permissions/permissions-internal-route-proxy-coverage.test.vitest.mjs

Updated suites include:

  • tests/vitests/suites/permissions/permissions-context-condition.test.vitest.mjs

  • tests/vitests/suites/permissions/permission-manager-context-fallback.test.vitest.mjs

  • tests/vitests/suites/metadata/metadata-external-api.test.vitest.mjs

  • tests/vitests/suites/metadata/metadata-edge-cases.test.vitest.mjs

  • Plus targeted updates in core/handler coverage to keep debug and wrapper behavior aligned with the new enforcement paths.