latticesql 1.1.2 → 1.2.2

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.
package/dist/cli.js CHANGED
@@ -1182,7 +1182,17 @@ var RenderEngine = class {
1182
1182
  }
1183
1183
  for (const entityRow of allRows) {
1184
1184
  const slug = def.slug(entityRow);
1185
+ if (/[^a-zA-Z0-9.\-_ @(),#&'+]/.test(slug)) {
1186
+ throw new Error(
1187
+ `Invalid slug "${slug}": contains characters outside the allowed set (alphanumeric, dot, hyphen, underscore, space, @, parens, comma, #, &, ', +)`
1188
+ );
1189
+ }
1185
1190
  const entityDir = def.directory ? join5(outputDir, def.directory(entityRow)) : join5(outputDir, directoryRoot, slug);
1191
+ const resolvedDir = resolve3(entityDir);
1192
+ const resolvedBase = resolve3(outputDir);
1193
+ if (!resolvedDir.startsWith(resolvedBase + "/") && resolvedDir !== resolvedBase) {
1194
+ throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
1195
+ }
1186
1196
  mkdirSync3(entityDir, { recursive: true });
1187
1197
  if (def.attachFileColumn) {
1188
1198
  const filePath = entityRow[def.attachFileColumn];
@@ -1219,8 +1229,10 @@ var RenderEngine = class {
1219
1229
  counters.skipped++;
1220
1230
  }
1221
1231
  }
1222
- if (def.combined && renderedFiles.size > 0) {
1223
- const excluded = new Set(def.combined.exclude ?? []);
1232
+ const fileKeys = Object.keys(def.files);
1233
+ const effectiveCombined = def.combined ?? (fileKeys.length > 1 && renderedFiles.size > 1 ? { outputFile: fileKeys[0] } : void 0);
1234
+ if (effectiveCombined && renderedFiles.size > 0) {
1235
+ const excluded = new Set(effectiveCombined.exclude ?? []);
1224
1236
  const parts = [];
1225
1237
  for (const filename of Object.keys(def.files)) {
1226
1238
  if (!excluded.has(filename) && renderedFiles.has(filename)) {
@@ -1229,14 +1241,14 @@ var RenderEngine = class {
1229
1241
  }
1230
1242
  if (parts.length > 0) {
1231
1243
  const combinedContent = parts.join("\n\n---\n\n");
1232
- const combinedPath = join5(entityDir, def.combined.outputFile);
1244
+ const combinedPath = join5(entityDir, effectiveCombined.outputFile);
1233
1245
  if (atomicWrite(combinedPath, combinedContent)) {
1234
1246
  filesWritten.push(combinedPath);
1235
1247
  } else {
1236
1248
  counters.skipped++;
1237
1249
  }
1238
- renderedFiles.set(def.combined.outputFile, combinedContent);
1239
- entityFileHashes[def.combined.outputFile] = { hash: contentHash(combinedContent) };
1250
+ renderedFiles.set(effectiveCombined.outputFile, combinedContent);
1251
+ entityFileHashes[effectiveCombined.outputFile] = { hash: contentHash(combinedContent) };
1240
1252
  }
1241
1253
  }
1242
1254
  manifestEntry.entities[slug] = entityFileHashes;
@@ -1343,6 +1355,9 @@ var ReverseSyncEngine = class {
1343
1355
  const pkCols = Object.keys(update.pk);
1344
1356
  if (pkCols.length === 0) continue;
1345
1357
  const colPattern = /^[a-zA-Z0-9_]+$/;
1358
+ if (!colPattern.test(update.table)) {
1359
+ throw new Error(`Invalid table name in reverse-sync update: ${update.table}`);
1360
+ }
1346
1361
  for (const col of [...setCols, ...pkCols]) {
1347
1362
  if (!colPattern.test(col)) {
1348
1363
  throw new Error(`Invalid column name in reverse-sync update: ${col}`);
package/dist/index.cjs CHANGED
@@ -942,7 +942,17 @@ var RenderEngine = class {
942
942
  }
943
943
  for (const entityRow of allRows) {
944
944
  const slug = def.slug(entityRow);
945
+ if (/[^a-zA-Z0-9.\-_ @(),#&'+]/.test(slug)) {
946
+ throw new Error(
947
+ `Invalid slug "${slug}": contains characters outside the allowed set (alphanumeric, dot, hyphen, underscore, space, @, parens, comma, #, &, ', +)`
948
+ );
949
+ }
945
950
  const entityDir = def.directory ? (0, import_node_path4.join)(outputDir, def.directory(entityRow)) : (0, import_node_path4.join)(outputDir, directoryRoot, slug);
951
+ const resolvedDir = (0, import_node_path4.resolve)(entityDir);
952
+ const resolvedBase = (0, import_node_path4.resolve)(outputDir);
953
+ if (!resolvedDir.startsWith(resolvedBase + "/") && resolvedDir !== resolvedBase) {
954
+ throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
955
+ }
946
956
  (0, import_node_fs4.mkdirSync)(entityDir, { recursive: true });
947
957
  if (def.attachFileColumn) {
948
958
  const filePath = entityRow[def.attachFileColumn];
@@ -979,8 +989,10 @@ var RenderEngine = class {
979
989
  counters.skipped++;
980
990
  }
981
991
  }
982
- if (def.combined && renderedFiles.size > 0) {
983
- const excluded = new Set(def.combined.exclude ?? []);
992
+ const fileKeys = Object.keys(def.files);
993
+ const effectiveCombined = def.combined ?? (fileKeys.length > 1 && renderedFiles.size > 1 ? { outputFile: fileKeys[0] } : void 0);
994
+ if (effectiveCombined && renderedFiles.size > 0) {
995
+ const excluded = new Set(effectiveCombined.exclude ?? []);
984
996
  const parts = [];
985
997
  for (const filename of Object.keys(def.files)) {
986
998
  if (!excluded.has(filename) && renderedFiles.has(filename)) {
@@ -989,14 +1001,14 @@ var RenderEngine = class {
989
1001
  }
990
1002
  if (parts.length > 0) {
991
1003
  const combinedContent = parts.join("\n\n---\n\n");
992
- const combinedPath = (0, import_node_path4.join)(entityDir, def.combined.outputFile);
1004
+ const combinedPath = (0, import_node_path4.join)(entityDir, effectiveCombined.outputFile);
993
1005
  if (atomicWrite(combinedPath, combinedContent)) {
994
1006
  filesWritten.push(combinedPath);
995
1007
  } else {
996
1008
  counters.skipped++;
997
1009
  }
998
- renderedFiles.set(def.combined.outputFile, combinedContent);
999
- entityFileHashes[def.combined.outputFile] = { hash: contentHash(combinedContent) };
1010
+ renderedFiles.set(effectiveCombined.outputFile, combinedContent);
1011
+ entityFileHashes[effectiveCombined.outputFile] = { hash: contentHash(combinedContent) };
1000
1012
  }
1001
1013
  }
1002
1014
  manifestEntry.entities[slug] = entityFileHashes;
@@ -1103,6 +1115,9 @@ var ReverseSyncEngine = class {
1103
1115
  const pkCols = Object.keys(update.pk);
1104
1116
  if (pkCols.length === 0) continue;
1105
1117
  const colPattern = /^[a-zA-Z0-9_]+$/;
1118
+ if (!colPattern.test(update.table)) {
1119
+ throw new Error(`Invalid table name in reverse-sync update: ${update.table}`);
1120
+ }
1106
1121
  for (const col of [...setCols, ...pkCols]) {
1107
1122
  if (!colPattern.test(col)) {
1108
1123
  throw new Error(`Invalid column name in reverse-sync update: ${col}`);
@@ -3154,9 +3169,14 @@ async function autoUpdate(opts) {
3154
3169
  if (!installed) return result;
3155
3170
  const latest = await getLatestVersion("latticesql");
3156
3171
  if (!latest || !isNewer(latest, installed)) return result;
3172
+ const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
3173
+ if (!SEMVER_RE.test(latest)) {
3174
+ console.error(`[latticesql] Rejecting invalid version: "${latest}"`);
3175
+ return result;
3176
+ }
3157
3177
  log(`[latticesql] Updating: latticesql@${installed} \u2192 ${latest}`);
3158
3178
  try {
3159
- (0, import_node_child_process.execSync)(`npm install latticesql@${latest}`, {
3179
+ (0, import_node_child_process.execFileSync)("npm", ["install", `latticesql@${latest}`], {
3160
3180
  cwd: process.cwd(),
3161
3181
  stdio: opts?.quiet ? "ignore" : "inherit",
3162
3182
  timeout: 6e4
package/dist/index.js CHANGED
@@ -874,7 +874,17 @@ var RenderEngine = class {
874
874
  }
875
875
  for (const entityRow of allRows) {
876
876
  const slug = def.slug(entityRow);
877
+ if (/[^a-zA-Z0-9.\-_ @(),#&'+]/.test(slug)) {
878
+ throw new Error(
879
+ `Invalid slug "${slug}": contains characters outside the allowed set (alphanumeric, dot, hyphen, underscore, space, @, parens, comma, #, &, ', +)`
880
+ );
881
+ }
877
882
  const entityDir = def.directory ? join4(outputDir, def.directory(entityRow)) : join4(outputDir, directoryRoot, slug);
883
+ const resolvedDir = resolve(entityDir);
884
+ const resolvedBase = resolve(outputDir);
885
+ if (!resolvedDir.startsWith(resolvedBase + "/") && resolvedDir !== resolvedBase) {
886
+ throw new Error(`Path traversal detected: slug "${slug}" escapes output directory`);
887
+ }
878
888
  mkdirSync2(entityDir, { recursive: true });
879
889
  if (def.attachFileColumn) {
880
890
  const filePath = entityRow[def.attachFileColumn];
@@ -911,8 +921,10 @@ var RenderEngine = class {
911
921
  counters.skipped++;
912
922
  }
913
923
  }
914
- if (def.combined && renderedFiles.size > 0) {
915
- const excluded = new Set(def.combined.exclude ?? []);
924
+ const fileKeys = Object.keys(def.files);
925
+ const effectiveCombined = def.combined ?? (fileKeys.length > 1 && renderedFiles.size > 1 ? { outputFile: fileKeys[0] } : void 0);
926
+ if (effectiveCombined && renderedFiles.size > 0) {
927
+ const excluded = new Set(effectiveCombined.exclude ?? []);
916
928
  const parts = [];
917
929
  for (const filename of Object.keys(def.files)) {
918
930
  if (!excluded.has(filename) && renderedFiles.has(filename)) {
@@ -921,14 +933,14 @@ var RenderEngine = class {
921
933
  }
922
934
  if (parts.length > 0) {
923
935
  const combinedContent = parts.join("\n\n---\n\n");
924
- const combinedPath = join4(entityDir, def.combined.outputFile);
936
+ const combinedPath = join4(entityDir, effectiveCombined.outputFile);
925
937
  if (atomicWrite(combinedPath, combinedContent)) {
926
938
  filesWritten.push(combinedPath);
927
939
  } else {
928
940
  counters.skipped++;
929
941
  }
930
- renderedFiles.set(def.combined.outputFile, combinedContent);
931
- entityFileHashes[def.combined.outputFile] = { hash: contentHash(combinedContent) };
942
+ renderedFiles.set(effectiveCombined.outputFile, combinedContent);
943
+ entityFileHashes[effectiveCombined.outputFile] = { hash: contentHash(combinedContent) };
932
944
  }
933
945
  }
934
946
  manifestEntry.entities[slug] = entityFileHashes;
@@ -1035,6 +1047,9 @@ var ReverseSyncEngine = class {
1035
1047
  const pkCols = Object.keys(update.pk);
1036
1048
  if (pkCols.length === 0) continue;
1037
1049
  const colPattern = /^[a-zA-Z0-9_]+$/;
1050
+ if (!colPattern.test(update.table)) {
1051
+ throw new Error(`Invalid table name in reverse-sync update: ${update.table}`);
1052
+ }
1038
1053
  for (const col of [...setCols, ...pkCols]) {
1039
1054
  if (!colPattern.test(col)) {
1040
1055
  throw new Error(`Invalid column name in reverse-sync update: ${col}`);
@@ -3044,7 +3059,7 @@ function applyWriteEntry(db, entry) {
3044
3059
  }
3045
3060
 
3046
3061
  // src/auto-update.ts
3047
- import { execSync } from "child_process";
3062
+ import { execFileSync } from "child_process";
3048
3063
  import { readFileSync as readFileSync6 } from "fs";
3049
3064
  import { join as join7 } from "path";
3050
3065
  function getInstalledVersion(pkgName) {
@@ -3086,9 +3101,14 @@ async function autoUpdate(opts) {
3086
3101
  if (!installed) return result;
3087
3102
  const latest = await getLatestVersion("latticesql");
3088
3103
  if (!latest || !isNewer(latest, installed)) return result;
3104
+ const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
3105
+ if (!SEMVER_RE.test(latest)) {
3106
+ console.error(`[latticesql] Rejecting invalid version: "${latest}"`);
3107
+ return result;
3108
+ }
3089
3109
  log(`[latticesql] Updating: latticesql@${installed} \u2192 ${latest}`);
3090
3110
  try {
3091
- execSync(`npm install latticesql@${latest}`, {
3111
+ execFileSync("npm", ["install", `latticesql@${latest}`], {
3092
3112
  cwd: process.cwd(),
3093
3113
  stdio: opts?.quiet ? "ignore" : "inherit",
3094
3114
  timeout: 6e4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latticesql",
3
- "version": "1.1.2",
3
+ "version": "1.2.2",
4
4
  "description": "Persistent structured memory for AI agent systems — SQLite ↔ LLM context bridge",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",