@character-foundry/character-foundry 0.4.2-dev.1765942273 → 0.4.2-dev.1765997746

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/index.js CHANGED
@@ -1339,36 +1339,6 @@ var SAFE_ASSET_TYPES = /* @__PURE__ */ new Set([
1339
1339
  "data",
1340
1340
  "unknown"
1341
1341
  ]);
1342
- var SAFE_EXTENSIONS = /* @__PURE__ */ new Set([
1343
- // Images
1344
- "png",
1345
- "jpg",
1346
- "jpeg",
1347
- "webp",
1348
- "gif",
1349
- "avif",
1350
- "svg",
1351
- "bmp",
1352
- "ico",
1353
- // Audio
1354
- "mp3",
1355
- "wav",
1356
- "ogg",
1357
- "flac",
1358
- "m4a",
1359
- "aac",
1360
- "opus",
1361
- // Video
1362
- "mp4",
1363
- "webm",
1364
- "avi",
1365
- "mov",
1366
- "mkv",
1367
- // Data
1368
- "json",
1369
- "txt",
1370
- "bin"
1371
- ]);
1372
1342
  function getCharxCategory(mimetype) {
1373
1343
  if (mimetype.startsWith("image/")) return "images";
1374
1344
  if (mimetype.startsWith("audio/")) return "audio";
@@ -1384,11 +1354,20 @@ function sanitizeAssetType(type) {
1384
1354
  return sanitized || "custom";
1385
1355
  }
1386
1356
  function sanitizeExtension(ext) {
1387
- const normalized = ext.replace(/^\./, "").toLowerCase().replace(/[^a-z0-9]/g, "");
1388
- if (SAFE_EXTENSIONS.has(normalized)) {
1389
- return normalized;
1357
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
1358
+ if (!normalized) {
1359
+ throw new Error("Invalid asset extension: empty extension");
1360
+ }
1361
+ if (normalized.length > 64) {
1362
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
1363
+ }
1364
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
1365
+ throw new Error("Invalid asset extension: path separators are not allowed");
1366
+ }
1367
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
1368
+ throw new Error(`Invalid asset extension: "${ext}"`);
1390
1369
  }
1391
- return "bin";
1370
+ return normalized;
1392
1371
  }
1393
1372
  function sanitizeName(name, ext) {
1394
1373
  let safeName = name;
@@ -8170,6 +8149,22 @@ function sanitizeName2(name, ext) {
8170
8149
  if (!safeName) safeName = "asset";
8171
8150
  return safeName;
8172
8151
  }
8152
+ function sanitizeExtension2(ext) {
8153
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
8154
+ if (!normalized) {
8155
+ throw new Error("Invalid asset extension: empty extension");
8156
+ }
8157
+ if (normalized.length > 64) {
8158
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
8159
+ }
8160
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
8161
+ throw new Error("Invalid asset extension: path separators are not allowed");
8162
+ }
8163
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
8164
+ throw new Error(`Invalid asset extension: "${ext}"`);
8165
+ }
8166
+ return normalized;
8167
+ }
8173
8168
  function writeVoxta(card, assets, options = {}) {
8174
8169
  const { compressionLevel = 6, includePackageJson = false } = options;
8175
8170
  const cardData = card.data;
@@ -8258,8 +8253,9 @@ function writeVoxta(card, assets, options = {}) {
8258
8253
  let assetCount = 0;
8259
8254
  let mainThumbnail;
8260
8255
  for (const asset of assets) {
8261
- const safeName = sanitizeName2(asset.name, asset.ext);
8262
- const finalFilename = `${safeName}.${asset.ext}`;
8256
+ const safeExt = sanitizeExtension2(asset.ext);
8257
+ const safeName = sanitizeName2(asset.name, safeExt);
8258
+ const finalFilename = `${safeName}.${safeExt}`;
8263
8259
  let voxtaPath = "";
8264
8260
  const tags = asset.tags || [];
8265
8261
  const isMainIcon = asset.type === "icon" && (tags.includes("portrait-override") || asset.name === "main" || asset.isMain);