locale-ai 0.6.9 → 0.6.10

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/README.md CHANGED
@@ -105,7 +105,7 @@ then declare it in `locale-ai.config.json`:
105
105
  }
106
106
  ```
107
107
 
108
- - `path` is a local folder (relative to the config file); `repo` is a Hugging Face repo with the converted files (uploaded output folder), downloaded and cached like built-in models.
108
+ - `path` is a local folder (relative to the config file); `repo` is a Hugging Face repo with the converted files (uploaded output folder), downloaded and cached like built-in models — it must look like `namespace/name`, rejected otherwise (it's joined onto the cache directory with no further checks, so anything else could resolve outside it).
109
109
  - `prefix` is required for multi-target models (`convert` lists the model's `>>xx<<` tokens).
110
110
  - Project models take priority over built-ins for their language pairs and can be used for pivoting (e.g. `ja → en → tr`).
111
111
 
package/dist/catalog.js CHANGED
@@ -90,10 +90,23 @@ export const CATALOG = [
90
90
  },
91
91
  ];
92
92
  const asList = (v) => (Array.isArray(v) ? v : [v]).map((x) => parseLocale(x).lang);
93
+ /**
94
+ * A Hugging Face repo id is always exactly "namespace/name". `models.ts` joins it
95
+ * onto the cache root with no further checks, so a `repo` of "../../../etc" (or any
96
+ * segment that isn't a real name) would resolve outside the cache directory — and
97
+ * install() writes there, uninstall() recursively deletes there.
98
+ */
99
+ function validRepo(repo) {
100
+ const parts = repo.split("/");
101
+ return parts.length === 2 && parts.every((p) => p !== "" && p !== "." && p !== ".." && !p.includes("\\"));
102
+ }
93
103
  export function customEntries(models, configDir) {
94
104
  return (models ?? []).map((c, i) => {
95
105
  if (!c.repo === !c.path)
96
106
  throw new Error(`models[${i}]: set exactly one of "repo" or "path".`);
107
+ if (c.repo && !validRepo(c.repo)) {
108
+ throw new Error(`models[${i}]: "repo" must look like "namespace/name" (a Hugging Face repo id), got "${c.repo}".`);
109
+ }
97
110
  if (!c.source || !c.target)
98
111
  throw new Error(`models[${i}]: "source" and "target" are required.`);
99
112
  const sources = asList(c.source);
package/dist/models.js CHANGED
@@ -25,8 +25,26 @@ export function cacheRoot() {
25
25
  return path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"), "locale-ai");
26
26
  }
27
27
  const modelsRoot = () => path.join(cacheRoot(), "models");
28
- /** Where a model's files live: its local folder, or its cache folder. */
29
- export const modelDir = (m) => m.path ?? path.join(modelsRoot(), ...m.repo.split("/"));
28
+ /**
29
+ * Where a model's files live: its local folder, or its cache folder. `repo` is
30
+ * validated as a plain "namespace/name" Hugging Face id when it comes from a
31
+ * project config (see catalog.ts's customEntries) — this is a second,
32
+ * independent check that the computed cache folder actually stays inside the
33
+ * cache root, since install() writes there and uninstall() recursively deletes
34
+ * it: a `repo` that somehow smuggled a ".." segment past that first check must
35
+ * never be allowed to touch a path outside the cache.
36
+ */
37
+ export function modelDir(m) {
38
+ if (m.path)
39
+ return m.path;
40
+ const root = modelsRoot();
41
+ const dir = path.join(root, ...m.repo.split("/"));
42
+ const rel = path.relative(root, dir);
43
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
44
+ throw new Error(`${m.id}: "repo" (${m.repo}) resolves outside the model cache.`);
45
+ }
46
+ return dir;
47
+ }
30
48
  export function readManifest(m) {
31
49
  if (m.path)
32
50
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locale-ai",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "Translate your i18n locale files locally on CPU. Free, offline, no API keys.",
5
5
  "author": "Taha KHABOUSS",
6
6
  "repository": {