prisma-guard 1.32.1 → 1.33.0

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.
@@ -227,7 +227,7 @@ function isJsonSafe(value) {
227
227
  }
228
228
  return true;
229
229
  }
230
- var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE]-?\d+)?$/;
230
+ var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
231
231
  var decimalStringSchema = import_zod.z.string().refine((s) => DECIMAL_REGEX.test(s), "Invalid decimal string");
232
232
  var decimalObjectSchema = import_zod.z.custom(
233
233
  (v) => v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function",
@@ -987,8 +987,13 @@ function matchVariantPatterns(keys, caller) {
987
987
  continue;
988
988
  let matchesCaller = true;
989
989
  for (let index = 0; index < patternParts.length; index++) {
990
- if (patternParts[index].startsWith(":"))
990
+ if (patternParts[index].startsWith(":")) {
991
+ if (callerParts[index].length === 0) {
992
+ matchesCaller = false;
993
+ break;
994
+ }
991
995
  continue;
996
+ }
992
997
  if (patternParts[index] !== callerParts[index]) {
993
998
  matchesCaller = false;
994
999
  break;
@@ -1083,8 +1088,12 @@ function deepClone(value) {
1083
1088
  case "object": {
1084
1089
  if (value instanceof Date)
1085
1090
  return new Date(value.getTime());
1086
- if (value instanceof Uint8Array)
1091
+ if (value instanceof Uint8Array) {
1092
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer?.(value)) {
1093
+ return Buffer.from(value);
1094
+ }
1087
1095
  return value.slice();
1096
+ }
1088
1097
  if (value instanceof RegExp)
1089
1098
  return new RegExp(value.source, value.flags);
1090
1099
  if (Array.isArray(value))
@@ -1094,7 +1103,17 @@ function deepClone(value) {
1094
1103
  return value;
1095
1104
  const result = {};
1096
1105
  for (const [k, v] of Object.entries(value)) {
1097
- result[k] = deepClone(v);
1106
+ const cloned = deepClone(v);
1107
+ if (k === "__proto__") {
1108
+ Object.defineProperty(result, k, {
1109
+ value: cloned,
1110
+ enumerable: true,
1111
+ writable: true,
1112
+ configurable: true
1113
+ });
1114
+ } else {
1115
+ result[k] = cloned;
1116
+ }
1098
1117
  }
1099
1118
  return result;
1100
1119
  }
@@ -1104,6 +1123,9 @@ function deepClone(value) {
1104
1123
  }
1105
1124
 
1106
1125
  // src/shared/deep-equal.ts
1126
+ function isDecimalLike(v) {
1127
+ return v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function";
1128
+ }
1107
1129
  function deepEqual(a, b) {
1108
1130
  if (a === b)
1109
1131
  return true;
@@ -1129,6 +1151,28 @@ function deepEqual(a, b) {
1129
1151
  }
1130
1152
  if (a instanceof RegExp || b instanceof RegExp)
1131
1153
  return false;
1154
+ if (a instanceof Uint8Array || b instanceof Uint8Array) {
1155
+ if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array))
1156
+ return false;
1157
+ if (a.byteLength !== b.byteLength)
1158
+ return false;
1159
+ for (let i = 0; i < a.byteLength; i++) {
1160
+ if (a[i] !== b[i])
1161
+ return false;
1162
+ }
1163
+ return true;
1164
+ }
1165
+ if (isDecimalLike(a) || isDecimalLike(b)) {
1166
+ if (!isDecimalLike(a) || !isDecimalLike(b))
1167
+ return false;
1168
+ try {
1169
+ if (typeof a.equals === "function")
1170
+ return a.equals(b) === true;
1171
+ return a.toString() === b.toString();
1172
+ } catch {
1173
+ return false;
1174
+ }
1175
+ }
1132
1176
  if (Array.isArray(a)) {
1133
1177
  if (!Array.isArray(b))
1134
1178
  return false;
@@ -2233,8 +2277,27 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
2233
2277
  );
2234
2278
  forcedOnlyKeys.add(key);
2235
2279
  }
2280
+ const selectorKeys = Object.keys(fieldSchemas);
2281
+ let schema;
2282
+ if (selectorKeys.length === 0) {
2283
+ schema = null;
2284
+ } else if (selectorKeys.length === 1) {
2285
+ schema = import_zod5.z.object(fieldSchemas).strict();
2286
+ } else {
2287
+ const optionalSchemas = {};
2288
+ for (const [k, s] of Object.entries(fieldSchemas)) {
2289
+ optionalSchemas[k] = s.optional();
2290
+ }
2291
+ const objectSchema = import_zod5.z.object(optionalSchemas).strict();
2292
+ schema = Object.keys(forcedConditions).length > 0 ? objectSchema : objectSchema.refine(
2293
+ (v) => selectorKeys.some((k) => v[k] !== void 0),
2294
+ {
2295
+ message: `Unique where for model "${model}" must specify at least one selector: ${selectorKeys.join(", ")}`
2296
+ }
2297
+ );
2298
+ }
2236
2299
  return {
2237
- schema: Object.keys(fieldSchemas).length > 0 ? import_zod5.z.object(fieldSchemas).strict() : null,
2300
+ schema,
2238
2301
  forced: {
2239
2302
  conditions: forcedConditions,
2240
2303
  relations: {}
@@ -3695,6 +3758,12 @@ function pickMissingFksFromResult(result, fks) {
3695
3758
  return missing;
3696
3759
  }
3697
3760
  function validateScopeValue(root, value) {
3761
+ const t = typeof value;
3762
+ if (t !== "string" && t !== "number" && t !== "bigint") {
3763
+ throw new PolicyError(
3764
+ `Non-primitive scope value for root "${root}" (${t}). Only string, number, and bigint are accepted \u2014 an object or array here would silently become a Prisma filter and could bypass scope isolation.`
3765
+ );
3766
+ }
3698
3767
  if (typeof value === "string" && value.length === 0) {
3699
3768
  throw new PolicyError(
3700
3769
  `Empty string scope value for root "${root}". This is almost certainly a bug in the context function.`
@@ -3737,10 +3806,12 @@ function enforceDataScope(data, scopes, overrides, log, model, operation, onScop
3737
3806
  }
3738
3807
  var VALID_FIND_UNIQUE_MODES = /* @__PURE__ */ new Set(["verify", "reject"]);
3739
3808
  var VALID_ON_SCOPE_RELATION_WRITES = /* @__PURE__ */ new Set(["error", "warn", "strip"]);
3809
+ var VALID_ON_MISSING_SCOPE_CONTEXT = /* @__PURE__ */ new Set(["error", "warn", "ignore"]);
3740
3810
  function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
3741
3811
  const log = logger ?? { warn: (msg) => console.warn(msg) };
3742
3812
  const findUniqueMode = guardConfig.findUniqueMode ?? "reject";
3743
3813
  const onScopeRelationWrite = guardConfig.onScopeRelationWrite ?? "error";
3814
+ const onMissingScopeContext = guardConfig.onMissingScopeContext ?? "error";
3744
3815
  if (!VALID_FIND_UNIQUE_MODES.has(findUniqueMode)) {
3745
3816
  throw new ShapeError(
3746
3817
  `prisma-guard: Invalid findUniqueMode "${findUniqueMode}". Allowed: ${[...VALID_FIND_UNIQUE_MODES].join(", ")}`
@@ -3751,6 +3822,11 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
3751
3822
  `prisma-guard: Invalid onScopeRelationWrite "${onScopeRelationWrite}". Allowed: ${[...VALID_ON_SCOPE_RELATION_WRITES].join(", ")}`
3752
3823
  );
3753
3824
  }
3825
+ if (!VALID_ON_MISSING_SCOPE_CONTEXT.has(onMissingScopeContext)) {
3826
+ throw new ShapeError(
3827
+ `prisma-guard: Invalid onMissingScopeContext "${onMissingScopeContext}". Allowed: ${[...VALID_ON_MISSING_SCOPE_CONTEXT].join(", ")}`
3828
+ );
3829
+ }
3754
3830
  const scopeFkSets = /* @__PURE__ */ new Map();
3755
3831
  for (const [model, entries] of Object.entries(scopeMap)) {
3756
3832
  const fks = /* @__PURE__ */ new Set();
@@ -3776,12 +3852,12 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
3776
3852
  const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
3777
3853
  const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
3778
3854
  if (missingRoots.length > 0) {
3779
- if (isMutation || guardConfig.onMissingScopeContext === "error") {
3855
+ if (isMutation || onMissingScopeContext === "error") {
3780
3856
  throw new PolicyError(
3781
3857
  `prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. All scope roots must be present.`
3782
3858
  );
3783
3859
  }
3784
- if (guardConfig.onMissingScopeContext === "warn") {
3860
+ if (onMissingScopeContext === "warn") {
3785
3861
  log.warn(
3786
3862
  `prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. Read proceeding with partial scope.`
3787
3863
  );