v3.4.1
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
gettrap resolves the full route path (e.g."slothlet.permissions.addRule") and callsenforceInternalPermission(routePath)before returning the value. - Non-configurable / non-writable properties return their value directly via
Reflect.getto satisfy JS proxy invariants (required to avoidTypeErrorwhenOwnershipManageriterates 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 acrossget,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
PermissionManagernow 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.mdby 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.mjstests/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.