@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/exporter.js CHANGED
@@ -514,36 +514,6 @@ var SAFE_ASSET_TYPES = /* @__PURE__ */ new Set([
514
514
  "data",
515
515
  "unknown"
516
516
  ]);
517
- var SAFE_EXTENSIONS = /* @__PURE__ */ new Set([
518
- // Images
519
- "png",
520
- "jpg",
521
- "jpeg",
522
- "webp",
523
- "gif",
524
- "avif",
525
- "svg",
526
- "bmp",
527
- "ico",
528
- // Audio
529
- "mp3",
530
- "wav",
531
- "ogg",
532
- "flac",
533
- "m4a",
534
- "aac",
535
- "opus",
536
- // Video
537
- "mp4",
538
- "webm",
539
- "avi",
540
- "mov",
541
- "mkv",
542
- // Data
543
- "json",
544
- "txt",
545
- "bin"
546
- ]);
547
517
  function getCharxCategory(mimetype) {
548
518
  if (mimetype.startsWith("image/")) return "images";
549
519
  if (mimetype.startsWith("audio/")) return "audio";
@@ -559,11 +529,20 @@ function sanitizeAssetType(type) {
559
529
  return sanitized || "custom";
560
530
  }
561
531
  function sanitizeExtension(ext) {
562
- const normalized = ext.replace(/^\./, "").toLowerCase().replace(/[^a-z0-9]/g, "");
563
- if (SAFE_EXTENSIONS.has(normalized)) {
564
- return normalized;
532
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
533
+ if (!normalized) {
534
+ throw new Error("Invalid asset extension: empty extension");
535
+ }
536
+ if (normalized.length > 64) {
537
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
538
+ }
539
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
540
+ throw new Error("Invalid asset extension: path separators are not allowed");
541
+ }
542
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
543
+ throw new Error(`Invalid asset extension: "${ext}"`);
565
544
  }
566
- return "bin";
545
+ return normalized;
567
546
  }
568
547
  function sanitizeName(name, ext) {
569
548
  let safeName = name;
@@ -7014,6 +6993,22 @@ function sanitizeName2(name, ext) {
7014
6993
  if (!safeName) safeName = "asset";
7015
6994
  return safeName;
7016
6995
  }
6996
+ function sanitizeExtension2(ext) {
6997
+ const normalized = ext.trim().replace(/^\./, "").toLowerCase();
6998
+ if (!normalized) {
6999
+ throw new Error("Invalid asset extension: empty extension");
7000
+ }
7001
+ if (normalized.length > 64) {
7002
+ throw new Error(`Invalid asset extension: too long (${normalized.length} chars)`);
7003
+ }
7004
+ if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
7005
+ throw new Error("Invalid asset extension: path separators are not allowed");
7006
+ }
7007
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(normalized)) {
7008
+ throw new Error(`Invalid asset extension: "${ext}"`);
7009
+ }
7010
+ return normalized;
7011
+ }
7017
7012
  function writeVoxta(card, assets, options = {}) {
7018
7013
  const { compressionLevel = 6, includePackageJson = false } = options;
7019
7014
  const cardData = card.data;
@@ -7102,8 +7097,9 @@ function writeVoxta(card, assets, options = {}) {
7102
7097
  let assetCount = 0;
7103
7098
  let mainThumbnail;
7104
7099
  for (const asset of assets) {
7105
- const safeName = sanitizeName2(asset.name, asset.ext);
7106
- const finalFilename = `${safeName}.${asset.ext}`;
7100
+ const safeExt = sanitizeExtension2(asset.ext);
7101
+ const safeName = sanitizeName2(asset.name, safeExt);
7102
+ const finalFilename = `${safeName}.${safeExt}`;
7107
7103
  let voxtaPath = "";
7108
7104
  const tags = asset.tags || [];
7109
7105
  const isMainIcon = asset.type === "icon" && (tags.includes("portrait-override") || asset.name === "main" || asset.isMain);