@elizaos/plugin-sql 1.6.4-alpha.15 → 1.6.4-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7937,7 +7937,7 @@ var init_migration_service = __esm(() => {
7937
7937
  });
7938
7938
 
7939
7939
  // src/index.node.ts
7940
- import { logger as logger12 } from "@elizaos/core";
7940
+ import { logger as logger13, stringToUuid } from "@elizaos/core";
7941
7941
 
7942
7942
  // src/pglite/adapter.ts
7943
7943
  import { logger as logger9 } from "@elizaos/core";
@@ -8173,6 +8173,7 @@ init_pg_core();
8173
8173
  var agentTable = pgTable("agents", {
8174
8174
  id: uuid("id").primaryKey().defaultRandom(),
8175
8175
  enabled: boolean("enabled").default(true).notNull(),
8176
+ owner_id: uuid("owner_id"),
8176
8177
  createdAt: timestamp("created_at", { withTimezone: true }).default(sql`now()`).notNull(),
8177
8178
  updatedAt: timestamp("updated_at", { withTimezone: true }).default(sql`now()`).notNull(),
8178
8179
  name: text("name").notNull(),
@@ -8321,6 +8322,7 @@ __export(exports_schema, {
8321
8322
  roomTable: () => roomTable,
8322
8323
  relationshipTable: () => relationshipTable,
8323
8324
  participantTable: () => participantTable,
8325
+ ownersTable: () => ownersTable,
8324
8326
  messageTable: () => messageTable,
8325
8327
  messageServerTable: () => messageServerTable,
8326
8328
  memoryTable: () => memoryTable,
@@ -8396,6 +8398,14 @@ var logTable = pgTable("logs", {
8396
8398
  foreignColumns: [entityTable.id]
8397
8399
  }).onDelete("cascade")
8398
8400
  ]);
8401
+ // src/schema/owners.ts
8402
+ init_drizzle_orm();
8403
+ init_pg_core();
8404
+ var ownersTable = pgTable("owners", {
8405
+ id: uuid("id").primaryKey(),
8406
+ createdAt: timestamp("created_at", { withTimezone: true }).default(sql`now()`).notNull(),
8407
+ updatedAt: timestamp("updated_at", { withTimezone: true }).default(sql`now()`).notNull()
8408
+ });
8399
8409
  // src/schema/participant.ts
8400
8410
  init_drizzle_orm();
8401
8411
  init_pg_core();
@@ -10943,8 +10953,13 @@ import { logger as logger11 } from "@elizaos/core";
10943
10953
  class PostgresConnectionManager {
10944
10954
  pool;
10945
10955
  db;
10946
- constructor(connectionString) {
10947
- this.pool = new Pool2({ connectionString });
10956
+ constructor(connectionString, rlsOwnerId) {
10957
+ const poolConfig = { connectionString };
10958
+ if (rlsOwnerId) {
10959
+ poolConfig.application_name = rlsOwnerId;
10960
+ logger11.debug(`[RLS] Pool configured with application_name: ${rlsOwnerId}`);
10961
+ }
10962
+ this.pool = new Pool2(poolConfig);
10948
10963
  this.db = drizzle2(this.pool);
10949
10964
  }
10950
10965
  getDatabase() {
@@ -11028,6 +11043,246 @@ function resolvePgliteDir(dir, fallbackDir) {
11028
11043
 
11029
11044
  // src/index.node.ts
11030
11045
  init_migration_service();
11046
+
11047
+ // src/rls.ts
11048
+ init_drizzle_orm();
11049
+ import { logger as logger12, validateUuid } from "@elizaos/core";
11050
+ async function installRLSFunctions(adapter) {
11051
+ const db2 = adapter.db;
11052
+ await db2.execute(sql`
11053
+ CREATE TABLE IF NOT EXISTS owners (
11054
+ id UUID PRIMARY KEY,
11055
+ created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
11056
+ updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
11057
+ )
11058
+ `);
11059
+ await db2.execute(sql`
11060
+ CREATE OR REPLACE FUNCTION current_owner_id() RETURNS UUID AS $$
11061
+ DECLARE
11062
+ app_name TEXT;
11063
+ BEGIN
11064
+ app_name := NULLIF(current_setting('application_name', TRUE), '');
11065
+
11066
+ -- Return NULL if application_name is not set or not a valid UUID
11067
+ -- This allows admin queries to work without RLS restrictions
11068
+ BEGIN
11069
+ RETURN app_name::UUID;
11070
+ EXCEPTION WHEN OTHERS THEN
11071
+ RETURN NULL;
11072
+ END;
11073
+ END;
11074
+ $$ LANGUAGE plpgsql STABLE;
11075
+ `);
11076
+ await db2.execute(sql`
11077
+ CREATE OR REPLACE FUNCTION add_owner_isolation(
11078
+ schema_name text,
11079
+ table_name text
11080
+ ) RETURNS void AS $$
11081
+ DECLARE
11082
+ full_table_name text;
11083
+ column_exists boolean;
11084
+ orphaned_count bigint;
11085
+ BEGIN
11086
+ full_table_name := schema_name || '.' || table_name;
11087
+
11088
+ -- Check if owner_id column already exists
11089
+ SELECT EXISTS (
11090
+ SELECT 1 FROM information_schema.columns
11091
+ WHERE information_schema.columns.table_schema = schema_name
11092
+ AND information_schema.columns.table_name = add_owner_isolation.table_name
11093
+ AND information_schema.columns.column_name = 'owner_id'
11094
+ ) INTO column_exists;
11095
+
11096
+ -- Add owner_id column if missing (DEFAULT populates it automatically for new rows)
11097
+ IF NOT column_exists THEN
11098
+ EXECUTE format('ALTER TABLE %I.%I ADD COLUMN owner_id UUID DEFAULT current_owner_id()', schema_name, table_name);
11099
+
11100
+ -- Backfill existing rows with current owner_id
11101
+ -- This ensures all existing data belongs to the tenant that is enabling RLS
11102
+ EXECUTE format('UPDATE %I.%I SET owner_id = current_owner_id() WHERE owner_id IS NULL', schema_name, table_name);
11103
+ ELSE
11104
+ -- Column already exists (RLS was previously enabled then disabled)
11105
+ -- Restore the DEFAULT clause (may have been removed during uninstallRLS)
11106
+ EXECUTE format('ALTER TABLE %I.%I ALTER COLUMN owner_id SET DEFAULT current_owner_id()', schema_name, table_name);
11107
+
11108
+ -- Only backfill NULL owner_id rows, do NOT steal data from other owners
11109
+ EXECUTE format('SELECT COUNT(*) FROM %I.%I WHERE owner_id IS NULL', schema_name, table_name) INTO orphaned_count;
11110
+
11111
+ IF orphaned_count > 0 THEN
11112
+ RAISE NOTICE 'Backfilling % rows with NULL owner_id in %.%', orphaned_count, schema_name, table_name;
11113
+ EXECUTE format('UPDATE %I.%I SET owner_id = current_owner_id() WHERE owner_id IS NULL', schema_name, table_name);
11114
+ END IF;
11115
+ END IF;
11116
+
11117
+ -- Create index for efficient owner_id filtering
11118
+ EXECUTE format('CREATE INDEX IF NOT EXISTS idx_%I_owner_id ON %I.%I(owner_id)', table_name, schema_name, table_name);
11119
+
11120
+ -- Enable RLS on the table
11121
+ EXECUTE format('ALTER TABLE %I.%I ENABLE ROW LEVEL SECURITY', schema_name, table_name);
11122
+
11123
+ -- FORCE RLS even for table owners (critical for security)
11124
+ EXECUTE format('ALTER TABLE %I.%I FORCE ROW LEVEL SECURITY', schema_name, table_name);
11125
+
11126
+ -- Drop existing policy if present
11127
+ EXECUTE format('DROP POLICY IF EXISTS owner_isolation_policy ON %I.%I', schema_name, table_name);
11128
+
11129
+ -- Create isolation policy: users can only see/modify rows where owner_id matches current tenant
11130
+ -- No NULL clause - all rows must have a valid owner_id (backfilled during column addition)
11131
+ EXECUTE format('
11132
+ CREATE POLICY owner_isolation_policy ON %I.%I
11133
+ USING (owner_id = current_owner_id())
11134
+ WITH CHECK (owner_id = current_owner_id())
11135
+ ', schema_name, table_name);
11136
+ END;
11137
+ $$ LANGUAGE plpgsql;
11138
+ `);
11139
+ await db2.execute(sql`
11140
+ CREATE OR REPLACE FUNCTION apply_rls_to_all_tables() RETURNS void AS $$
11141
+ DECLARE
11142
+ tbl record;
11143
+ BEGIN
11144
+ FOR tbl IN
11145
+ SELECT schemaname, tablename
11146
+ FROM pg_tables
11147
+ WHERE schemaname = 'public'
11148
+ AND tablename NOT IN (
11149
+ 'owners',
11150
+ 'drizzle_migrations',
11151
+ '__drizzle_migrations',
11152
+ 'server_agents'
11153
+ )
11154
+ LOOP
11155
+ BEGIN
11156
+ PERFORM add_owner_isolation(tbl.schemaname, tbl.tablename);
11157
+ EXCEPTION WHEN OTHERS THEN
11158
+ RAISE WARNING 'Failed to apply RLS to %.%: %', tbl.schemaname, tbl.tablename, SQLERRM;
11159
+ END;
11160
+ END LOOP;
11161
+ END;
11162
+ $$ LANGUAGE plpgsql;
11163
+ `);
11164
+ logger12.info("[RLS] PostgreSQL functions installed");
11165
+ }
11166
+ async function getOrCreateRlsOwner(adapter, ownerId) {
11167
+ const db2 = adapter.db;
11168
+ await db2.insert(ownersTable).values({
11169
+ id: ownerId
11170
+ }).onConflictDoNothing();
11171
+ logger12.info(`[RLS] Owner: ${ownerId.slice(0, 8)}…`);
11172
+ return ownerId;
11173
+ }
11174
+ async function setOwnerContext(adapter, ownerId) {
11175
+ if (!validateUuid(ownerId)) {
11176
+ throw new Error(`Invalid owner ID format: ${ownerId}. Must be a valid UUID.`);
11177
+ }
11178
+ const db2 = adapter.db;
11179
+ const owners = await db2.select().from(ownersTable).where(eq(ownersTable.id, ownerId));
11180
+ if (owners.length === 0) {
11181
+ throw new Error(`Owner ${ownerId} does not exist`);
11182
+ }
11183
+ logger12.info(`[RLS] Owner: ${ownerId.slice(0, 8)}…`);
11184
+ logger12.info("[RLS] Context configured successfully (using application_name)");
11185
+ }
11186
+ async function assignAgentToOwner(adapter, agentId, ownerId) {
11187
+ const db2 = adapter.db;
11188
+ const agents = await db2.select().from(agentTable).where(eq(agentTable.id, agentId));
11189
+ if (agents.length > 0) {
11190
+ const agent = agents[0];
11191
+ const currentOwnerId = agent.owner_id;
11192
+ if (currentOwnerId === ownerId) {
11193
+ logger12.debug(`[RLS] Agent ${agent.name} already owned by correct owner`);
11194
+ } else {
11195
+ await db2.update(agentTable).set({ owner_id: ownerId }).where(eq(agentTable.id, agentId));
11196
+ if (currentOwnerId === null) {
11197
+ logger12.info(`[RLS] Agent ${agent.name} assigned to owner`);
11198
+ } else {
11199
+ logger12.warn(`[RLS] Agent ${agent.name} owner changed`);
11200
+ }
11201
+ }
11202
+ } else {
11203
+ logger12.debug(`[RLS] Agent ${agentId} doesn't exist yet`);
11204
+ }
11205
+ }
11206
+ async function applyRLSToNewTables(adapter) {
11207
+ const db2 = adapter.db;
11208
+ try {
11209
+ await db2.execute(sql`SELECT apply_rls_to_all_tables()`);
11210
+ logger12.info("[RLS] Applied to all tables");
11211
+ } catch (error) {
11212
+ logger12.warn("[RLS] Failed to apply to some tables:", String(error));
11213
+ }
11214
+ }
11215
+ async function uninstallRLS(adapter) {
11216
+ const db2 = adapter.db;
11217
+ try {
11218
+ const checkResult = await db2.execute(sql`
11219
+ SELECT EXISTS (
11220
+ SELECT FROM pg_tables
11221
+ WHERE schemaname = 'public' AND tablename = 'owners'
11222
+ ) as rls_enabled
11223
+ `);
11224
+ const rlsEnabled = checkResult.rows?.[0]?.rls_enabled;
11225
+ if (!rlsEnabled) {
11226
+ logger12.debug("[RLS] RLS not installed, skipping cleanup");
11227
+ return;
11228
+ }
11229
+ logger12.info("[RLS] Disabling RLS globally (keeping owner_id columns for schema compatibility)...");
11230
+ await db2.execute(sql`
11231
+ CREATE OR REPLACE FUNCTION _temp_disable_rls_on_table(
11232
+ p_schema_name text,
11233
+ p_table_name text
11234
+ ) RETURNS void AS $$
11235
+ DECLARE
11236
+ policy_rec record;
11237
+ BEGIN
11238
+ -- Drop all policies on this table
11239
+ FOR policy_rec IN
11240
+ SELECT policyname
11241
+ FROM pg_policies
11242
+ WHERE schemaname = p_schema_name AND tablename = p_table_name
11243
+ LOOP
11244
+ EXECUTE format('DROP POLICY IF EXISTS %I ON %I.%I',
11245
+ policy_rec.policyname, p_schema_name, p_table_name);
11246
+ END LOOP;
11247
+
11248
+ -- Disable RLS
11249
+ EXECUTE format('ALTER TABLE %I.%I NO FORCE ROW LEVEL SECURITY', p_schema_name, p_table_name);
11250
+ EXECUTE format('ALTER TABLE %I.%I DISABLE ROW LEVEL SECURITY', p_schema_name, p_table_name);
11251
+ END;
11252
+ $$ LANGUAGE plpgsql;
11253
+ `);
11254
+ const tablesResult = await db2.execute(sql`
11255
+ SELECT schemaname, tablename
11256
+ FROM pg_tables
11257
+ WHERE schemaname = 'public'
11258
+ AND tablename NOT IN ('drizzle_migrations', '__drizzle_migrations')
11259
+ `);
11260
+ for (const row of tablesResult.rows || []) {
11261
+ const schemaName = row.schemaname;
11262
+ const tableName = row.tablename;
11263
+ try {
11264
+ await db2.execute(sql`SELECT _temp_disable_rls_on_table(${schemaName}, ${tableName})`);
11265
+ logger12.debug(`[RLS] Disabled RLS on table: ${schemaName}.${tableName}`);
11266
+ } catch (error) {
11267
+ logger12.warn(`[RLS] Failed to disable RLS on table ${schemaName}.${tableName}:`, String(error));
11268
+ }
11269
+ }
11270
+ await db2.execute(sql`DROP FUNCTION IF EXISTS _temp_disable_rls_on_table(text, text)`);
11271
+ logger12.info("[RLS] Keeping owner_id values intact (prevents data theft on re-enable)");
11272
+ logger12.info("[RLS] Clearing owners table...");
11273
+ await db2.execute(sql`TRUNCATE TABLE owners`);
11274
+ await db2.execute(sql`DROP FUNCTION IF EXISTS apply_rls_to_all_tables() CASCADE`);
11275
+ await db2.execute(sql`DROP FUNCTION IF EXISTS add_owner_isolation(text, text) CASCADE`);
11276
+ await db2.execute(sql`DROP FUNCTION IF EXISTS current_owner_id() CASCADE`);
11277
+ logger12.info("[RLS] Dropped all RLS functions");
11278
+ logger12.success("[RLS] RLS disabled successfully (owner_id columns preserved)");
11279
+ } catch (error) {
11280
+ logger12.error("[RLS] Failed to disable RLS:", String(error));
11281
+ throw error;
11282
+ }
11283
+ }
11284
+
11285
+ // src/index.node.ts
11031
11286
  var GLOBAL_SINGLETONS = Symbol.for("@elizaos/plugin-sql/global-singletons");
11032
11287
  var globalSymbols = globalThis;
11033
11288
  if (!globalSymbols[GLOBAL_SINGLETONS]) {
@@ -11036,10 +11291,28 @@ if (!globalSymbols[GLOBAL_SINGLETONS]) {
11036
11291
  var globalSingletons = globalSymbols[GLOBAL_SINGLETONS];
11037
11292
  function createDatabaseAdapter(config, agentId) {
11038
11293
  if (config.postgresUrl) {
11039
- if (!globalSingletons.postgresConnectionManager) {
11040
- globalSingletons.postgresConnectionManager = new PostgresConnectionManager(config.postgresUrl);
11294
+ const rlsEnabled = process.env.ENABLE_RLS_ISOLATION === "true";
11295
+ let rlsOwnerId;
11296
+ let managerKey = "default";
11297
+ if (rlsEnabled) {
11298
+ const rlsOwnerIdString = process.env.RLS_OWNER_ID;
11299
+ if (!rlsOwnerIdString) {
11300
+ throw new Error("[RLS] ENABLE_RLS_ISOLATION=true requires RLS_OWNER_ID environment variable");
11301
+ }
11302
+ rlsOwnerId = stringToUuid(rlsOwnerIdString);
11303
+ managerKey = rlsOwnerId;
11304
+ logger13.debug(`[RLS] Using connection pool for owner_id: ${rlsOwnerId.slice(0, 8)}… (from RLS_OWNER_ID="${rlsOwnerIdString}")`);
11305
+ }
11306
+ if (!globalSingletons.postgresConnectionManagers) {
11307
+ globalSingletons.postgresConnectionManagers = new Map;
11308
+ }
11309
+ let manager = globalSingletons.postgresConnectionManagers.get(managerKey);
11310
+ if (!manager) {
11311
+ logger13.debug(`[RLS] Creating new connection pool for key: ${managerKey.slice(0, 8)}…`);
11312
+ manager = new PostgresConnectionManager(config.postgresUrl, rlsOwnerId);
11313
+ globalSingletons.postgresConnectionManagers.set(managerKey, manager);
11041
11314
  }
11042
- return new PgDatabaseAdapter(agentId, globalSingletons.postgresConnectionManager);
11315
+ return new PgDatabaseAdapter(agentId, manager);
11043
11316
  }
11044
11317
  const dataDir = resolvePgliteDir(config.dataDir);
11045
11318
  if (!globalSingletons.pgLiteClientManager) {
@@ -11053,18 +11326,18 @@ var plugin = {
11053
11326
  priority: 0,
11054
11327
  schema: exports_schema,
11055
11328
  init: async (_config, runtime) => {
11056
- logger12.info("plugin-sql (node) init starting...");
11329
+ logger13.info("plugin-sql (node) init starting...");
11057
11330
  const adapterRegistered = await runtime.isReady().then(() => true).catch((error) => {
11058
11331
  const message = error instanceof Error ? error.message : String(error);
11059
11332
  if (message.includes("Database adapter not registered")) {
11060
- logger12.info("No pre-registered database adapter detected; registering adapter");
11333
+ logger13.info("No pre-registered database adapter detected; registering adapter");
11061
11334
  } else {
11062
- logger12.warn({ error }, "Database adapter readiness check error; proceeding to register adapter");
11335
+ logger13.warn({ error }, "Database adapter readiness check error; proceeding to register adapter");
11063
11336
  }
11064
11337
  return false;
11065
11338
  });
11066
11339
  if (adapterRegistered) {
11067
- logger12.info("Database adapter already registered, skipping creation");
11340
+ logger13.info("Database adapter already registered, skipping creation");
11068
11341
  return;
11069
11342
  }
11070
11343
  const postgresUrl = runtime.getSetting("POSTGRES_URL");
@@ -11074,16 +11347,22 @@ var plugin = {
11074
11347
  postgresUrl
11075
11348
  }, runtime.agentId);
11076
11349
  runtime.registerDatabaseAdapter(dbAdapter);
11077
- logger12.info("Database adapter created and registered");
11350
+ logger13.info("Database adapter created and registered");
11078
11351
  }
11079
11352
  };
11080
11353
  var index_node_default = plugin;
11081
11354
  export {
11355
+ uninstallRLS,
11356
+ setOwnerContext,
11082
11357
  plugin,
11358
+ installRLSFunctions,
11359
+ getOrCreateRlsOwner,
11083
11360
  index_node_default as default,
11084
11361
  createDatabaseAdapter,
11362
+ assignAgentToOwner,
11363
+ applyRLSToNewTables,
11085
11364
  DatabaseMigrationService
11086
11365
  };
11087
11366
 
11088
- //# debugId=AE1FE5F77043850164756E2164756E21
11367
+ //# debugId=81727AD4C0F30EE564756E2164756E21
11089
11368
  //# sourceMappingURL=index.node.js.map