Skip to content

Reject extension keys that overwrite scoped DB core methods #49

Description

@benvinegar

Summary

extensions(...) are assigned onto the scoped DB object with Object.assign(...). An extension can accidentally or intentionally overwrite guarded core methods/properties such as select, insert, update, delete, query, transaction, or the unsafe/raw escape property. That can silently bypass the scoped facade.

Evidence

Relevant code: src/scoped-db.ts:

if (options.extensions) {
  Object.assign(scoped, options.extensions(options.scopeValue, options.scopeName));
}

No reserved-key check is performed before assignment.

Risk example

const scopedDb = createScopedDb(db, {
  scopeName: "workspace",
  scopeValue: workspaceId,
  rules: [scopeByColumn(projects, projects.workspaceId)],
  extensions: () => ({
    select: db.select.bind(db), // accidental name collision or attempted helper
  }),
});

// Now this may call raw Drizzle select instead of the scoped select facade.
await scopedDb.select().from(projects);

Expected behavior

Extension keys should not be allowed to overwrite built-in scoped API methods or configured escape properties.

Suggested fix

Before assigning extensions, validate keys against a reserved set, including at least:

  • select
  • selectDistinct
  • selectDistinctOn
  • insert
  • update
  • delete
  • query
  • transaction
  • toJSON
  • configured unscopedDbPropertyName
  • configured scopeValueProperty, if present

If any extension key collides, throw a clear error at createScopedDb(...) time.

Also consider using Object.defineProperty for core methods as non-writable, but explicit validation provides better user errors.

Tests

  1. Unit test that extensions: () => ({ select: () => ... }) throws a clear reserved-key error.
  2. Repeat for query, transaction, custom unscopedDbPropertyName, and custom scopeValueProperty.
  3. Assert non-conflicting extension methods still work on root wrappers and transaction wrappers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions