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.
@@ -196,7 +196,7 @@ function isJsonSafe(value) {
196
196
  }
197
197
  return true;
198
198
  }
199
- var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE]-?\d+)?$/;
199
+ var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
200
200
  var decimalStringSchema = z.string().refine((s) => DECIMAL_REGEX.test(s), "Invalid decimal string");
201
201
  var decimalObjectSchema = z.custom(
202
202
  (v) => v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function",
@@ -956,8 +956,13 @@ function matchVariantPatterns(keys, caller) {
956
956
  continue;
957
957
  let matchesCaller = true;
958
958
  for (let index = 0; index < patternParts.length; index++) {
959
- if (patternParts[index].startsWith(":"))
959
+ if (patternParts[index].startsWith(":")) {
960
+ if (callerParts[index].length === 0) {
961
+ matchesCaller = false;
962
+ break;
963
+ }
960
964
  continue;
965
+ }
961
966
  if (patternParts[index] !== callerParts[index]) {
962
967
  matchesCaller = false;
963
968
  break;
@@ -1052,8 +1057,12 @@ function deepClone(value) {
1052
1057
  case "object": {
1053
1058
  if (value instanceof Date)
1054
1059
  return new Date(value.getTime());
1055
- if (value instanceof Uint8Array)
1060
+ if (value instanceof Uint8Array) {
1061
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer?.(value)) {
1062
+ return Buffer.from(value);
1063
+ }
1056
1064
  return value.slice();
1065
+ }
1057
1066
  if (value instanceof RegExp)
1058
1067
  return new RegExp(value.source, value.flags);
1059
1068
  if (Array.isArray(value))
@@ -1063,7 +1072,17 @@ function deepClone(value) {
1063
1072
  return value;
1064
1073
  const result = {};
1065
1074
  for (const [k, v] of Object.entries(value)) {
1066
- result[k] = deepClone(v);
1075
+ const cloned = deepClone(v);
1076
+ if (k === "__proto__") {
1077
+ Object.defineProperty(result, k, {
1078
+ value: cloned,
1079
+ enumerable: true,
1080
+ writable: true,
1081
+ configurable: true
1082
+ });
1083
+ } else {
1084
+ result[k] = cloned;
1085
+ }
1067
1086
  }
1068
1087
  return result;
1069
1088
  }
@@ -1073,6 +1092,9 @@ function deepClone(value) {
1073
1092
  }
1074
1093
 
1075
1094
  // src/shared/deep-equal.ts
1095
+ function isDecimalLike(v) {
1096
+ return v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function";
1097
+ }
1076
1098
  function deepEqual(a, b) {
1077
1099
  if (a === b)
1078
1100
  return true;
@@ -1098,6 +1120,28 @@ function deepEqual(a, b) {
1098
1120
  }
1099
1121
  if (a instanceof RegExp || b instanceof RegExp)
1100
1122
  return false;
1123
+ if (a instanceof Uint8Array || b instanceof Uint8Array) {
1124
+ if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array))
1125
+ return false;
1126
+ if (a.byteLength !== b.byteLength)
1127
+ return false;
1128
+ for (let i = 0; i < a.byteLength; i++) {
1129
+ if (a[i] !== b[i])
1130
+ return false;
1131
+ }
1132
+ return true;
1133
+ }
1134
+ if (isDecimalLike(a) || isDecimalLike(b)) {
1135
+ if (!isDecimalLike(a) || !isDecimalLike(b))
1136
+ return false;
1137
+ try {
1138
+ if (typeof a.equals === "function")
1139
+ return a.equals(b) === true;
1140
+ return a.toString() === b.toString();
1141
+ } catch {
1142
+ return false;
1143
+ }
1144
+ }
1101
1145
  if (Array.isArray(a)) {
1102
1146
  if (!Array.isArray(b))
1103
1147
  return false;
@@ -2202,8 +2246,27 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
2202
2246
  );
2203
2247
  forcedOnlyKeys.add(key);
2204
2248
  }
2249
+ const selectorKeys = Object.keys(fieldSchemas);
2250
+ let schema;
2251
+ if (selectorKeys.length === 0) {
2252
+ schema = null;
2253
+ } else if (selectorKeys.length === 1) {
2254
+ schema = z5.object(fieldSchemas).strict();
2255
+ } else {
2256
+ const optionalSchemas = {};
2257
+ for (const [k, s] of Object.entries(fieldSchemas)) {
2258
+ optionalSchemas[k] = s.optional();
2259
+ }
2260
+ const objectSchema = z5.object(optionalSchemas).strict();
2261
+ schema = Object.keys(forcedConditions).length > 0 ? objectSchema : objectSchema.refine(
2262
+ (v) => selectorKeys.some((k) => v[k] !== void 0),
2263
+ {
2264
+ message: `Unique where for model "${model}" must specify at least one selector: ${selectorKeys.join(", ")}`
2265
+ }
2266
+ );
2267
+ }
2205
2268
  return {
2206
- schema: Object.keys(fieldSchemas).length > 0 ? z5.object(fieldSchemas).strict() : null,
2269
+ schema,
2207
2270
  forced: {
2208
2271
  conditions: forcedConditions,
2209
2272
  relations: {}
@@ -3664,6 +3727,12 @@ function pickMissingFksFromResult(result, fks) {
3664
3727
  return missing;
3665
3728
  }
3666
3729
  function validateScopeValue(root, value) {
3730
+ const t = typeof value;
3731
+ if (t !== "string" && t !== "number" && t !== "bigint") {
3732
+ throw new PolicyError(
3733
+ `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.`
3734
+ );
3735
+ }
3667
3736
  if (typeof value === "string" && value.length === 0) {
3668
3737
  throw new PolicyError(
3669
3738
  `Empty string scope value for root "${root}". This is almost certainly a bug in the context function.`
@@ -3706,10 +3775,12 @@ function enforceDataScope(data, scopes, overrides, log, model, operation, onScop
3706
3775
  }
3707
3776
  var VALID_FIND_UNIQUE_MODES = /* @__PURE__ */ new Set(["verify", "reject"]);
3708
3777
  var VALID_ON_SCOPE_RELATION_WRITES = /* @__PURE__ */ new Set(["error", "warn", "strip"]);
3778
+ var VALID_ON_MISSING_SCOPE_CONTEXT = /* @__PURE__ */ new Set(["error", "warn", "ignore"]);
3709
3779
  function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
3710
3780
  const log = logger ?? { warn: (msg) => console.warn(msg) };
3711
3781
  const findUniqueMode = guardConfig.findUniqueMode ?? "reject";
3712
3782
  const onScopeRelationWrite = guardConfig.onScopeRelationWrite ?? "error";
3783
+ const onMissingScopeContext = guardConfig.onMissingScopeContext ?? "error";
3713
3784
  if (!VALID_FIND_UNIQUE_MODES.has(findUniqueMode)) {
3714
3785
  throw new ShapeError(
3715
3786
  `prisma-guard: Invalid findUniqueMode "${findUniqueMode}". Allowed: ${[...VALID_FIND_UNIQUE_MODES].join(", ")}`
@@ -3720,6 +3791,11 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
3720
3791
  `prisma-guard: Invalid onScopeRelationWrite "${onScopeRelationWrite}". Allowed: ${[...VALID_ON_SCOPE_RELATION_WRITES].join(", ")}`
3721
3792
  );
3722
3793
  }
3794
+ if (!VALID_ON_MISSING_SCOPE_CONTEXT.has(onMissingScopeContext)) {
3795
+ throw new ShapeError(
3796
+ `prisma-guard: Invalid onMissingScopeContext "${onMissingScopeContext}". Allowed: ${[...VALID_ON_MISSING_SCOPE_CONTEXT].join(", ")}`
3797
+ );
3798
+ }
3723
3799
  const scopeFkSets = /* @__PURE__ */ new Map();
3724
3800
  for (const [model, entries] of Object.entries(scopeMap)) {
3725
3801
  const fks = /* @__PURE__ */ new Set();
@@ -3745,12 +3821,12 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
3745
3821
  const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
3746
3822
  const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
3747
3823
  if (missingRoots.length > 0) {
3748
- if (isMutation || guardConfig.onMissingScopeContext === "error") {
3824
+ if (isMutation || onMissingScopeContext === "error") {
3749
3825
  throw new PolicyError(
3750
3826
  `prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. All scope roots must be present.`
3751
3827
  );
3752
3828
  }
3753
- if (guardConfig.onMissingScopeContext === "warn") {
3829
+ if (onMissingScopeContext === "warn") {
3754
3830
  log.warn(
3755
3831
  `prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. Read proceeding with partial scope.`
3756
3832
  );