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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions