Skip to content

Array-related functions (has, hasSome) in access policies fail on PostgreSQL #595

@WakuwakuP

Description

@WakuwakuP

Description

When using array-related functions (has, hasSome) in access policies with PostgreSQL, the policy evaluation fails with different errors depending on the function used:

  1. has() function: Produces malformed array literal error
  2. hasSome() function: Produces Unsupported argument expression: array error

These functions are essential for implementing role-based access control with array fields in the AuthUser type.

Environment

  • ZenStack version: 3.2.1
  • Database: PostgreSQL
  • Node.js version: 24.x

Schema to Reproduce

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

plugin policy {
  provider = '@zenstackhq/plugin-policy'
}

enum Role {
  admin
  editor
  viewer
}

type AuthUser {
  id    String
  roles Role[]

  @@auth
}

model User {
  id       String    @id @default(cuid())
  name     String
  email    String?   @unique
  posts    Post[]
  profile  Profile?
  
  @@allow('read', auth() != null)
  @@allow('update', auth().id == this.id)
  // has() produces "malformed array literal" error
  @@allow('all', has(auth().roles, admin))
}

model Profile {
  id        String  @id @default(cuid())
  userId    String  @unique
  bio       String?
  
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
  
  @@allow('read', auth() != null)
  @@allow('create', userId == auth().id)
  @@allow('update', userId == auth().id)
  // has() produces "malformed array literal" error
  @@allow('all', has(auth().roles, admin))
}

model Post {
  id        String   @id @default(cuid())
  title     String
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  
  @@allow('read', auth() != null)
  @@allow('create', authorId == auth().id)
  // hasSome() produces "Unsupported argument expression: array" error
  @@allow('update', hasSome(auth().roles, [admin, editor]))
}

Code to Reproduce

import { ZenStackClient } from '@zenstackhq/orm';
import { schema } from './zenstack/schema';
import { Role } from 'zenstack/models'
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = new ZenStackClient(schema, {
  dialect: { pool, type: 'postgres' }
});

// Set auth context with roles array
const userId = 'user-123'
const userDb = db.$setAuth({
  id: userId ,
  roles: [ Role.editor ],
});

// has() - "malformed array literal" error
// This happens during policy evaluation for nested mutations
await userDb.user.update({
  data: {
    profile: {
      upsert: {
        create: { bio: 'Hello' },
        update: { bio: 'Hello' },
        where: { userId },
      },
    },
  },
  where: { id: userId },
});

// hasSome() - "Unsupported argument expression: array" error
const postId = 'post-123'
await userDb.post.update({
  data: { title: 'Updated Title' },
  where: { id: postId },
});

Error Messages

has() function

Error: Failed to execute query: error: malformed array literal: "admin"
  reason: 'db-query-error',
  dbErrorCode: '22P02',
  dbErrorMessage: 'malformed array literal: "admin"',
  sql: `... cast(ARRAY[$1,$2] as "public"."Role"[]) @> ($3) ...`,
  detail: 'Array value must start with "{" or dimension information.',

hasSome() function

Error: Failed to execute query: Error: Unsupported argument expression: array
  reason: 'not-supported',
  dbErrorMessage: 'Unsupported argument expression: array',

Root Cause Analysis

has() in @zenstackhq/orm

Location: @zenstackhq/orm/dist/index.js - has() function

var has = __name((eb, args) => {
  const [field, search2] = args;
  // ...
  return eb(field, "@>", [search2]);  // ← Problem here
}, "has");

When search2 is a Kysely Expression object (returned from eb.val()), wrapping it in [search2] creates a JavaScript array containing the Expression object rather than a proper SQL array value. This causes PostgreSQL to receive a malformed array literal.

hasSome() in @zenstackhq/plugin-policy

Location: @zenstackhq/plugin-policy/dist/index.js - transformCallArg() function

transformCallArg(eb, arg, context) {
  if (ExpressionUtils3.isLiteral(arg)) { return eb.val(arg.value); }
  if (ExpressionUtils3.isField(arg)) { return eb.ref(arg.field); }
  if (ExpressionUtils3.isCall(arg)) { return this.transformCall(arg, context); }
  if (this.isAuthMember(arg)) { /* ... */ }
  // Missing: handling for ExpressionUtils.isArray(arg)
  throw createUnsupportedError(`Unsupported argument expression: ${arg.kind}`);
}

The function does not handle array expressions (ExpressionUtils.isArray(arg)), which are required for hasSome([admin, editor]) syntax.

Summary

Function Error Affected Package
has() malformed array literal @zenstackhq/orm
hasSome() Unsupported argument expression: array @zenstackhq/plugin-policy

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions