janhq/jan · error · Error

Invalid modelId: ${modelId}. Only alphanumeric and / _ - . c

Error message

Invalid modelId: ${modelId}. Only alphanumeric and / _ - . characters are allowed.

What it means

Thrown by runImport() (the private core of import()) when modelId fails isValidModelId. The validator enforces two rules: the regex ^[a-zA-Z0-9/_\-\.]+$ (only alphanumerics, slash, underscore, hyphen, dot), and after splitting on '/', no segment may be empty, '.', or '..' (path-traversal and empty-part guard). This blocks both invalid characters and directory-escape attempts before the id is joined into a filesystem path.

Source

Thrown at extensions/llamacpp-extension/src/index.ts:3086

    const task = this.runImport(modelId, opts).finally(() => {
      this.pendingImports.delete(modelId)
    })
    this.pendingImports.set(modelId, task)
    return task
  }

  private async runImport(modelId: string, opts: ImportOptions): Promise<void> {
    const isValidModelId = (id: string) => {
      // only allow alphanumeric, underscore, hyphen, and dot characters in modelId
      if (!/^[a-zA-Z0-9/_\-\.]+$/.test(id)) return false

      // check for empty parts or path traversal
      const parts = id.split('/')
      return parts.every((s) => s !== '' && s !== '.' && s !== '..')
    }

    if (!isValidModelId(modelId))
      throw new Error(
        `Invalid modelId: ${modelId}. Only alphanumeric and / _ - . characters are allowed.`
      )

    const configPath = await joinPath([
      await this.getProviderPath(),
      'models',
      modelId,
      'model.yml',
    ])
    if (await fs.existsSync(configPath))
      throw new Error(`Model ${modelId} already exists`)

    // this is relative to Jan's data folder
    const modelDir = `${this.providerId}/models/${modelId}`

    // we only use these from opts
    // opts.modelPath: URL to the model file
    // opts.mmprojPath: URL to the mmproj file

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Sanitize modelId to only [a-zA-Z0-9/_-.] before calling import - replace spaces with hyphens, strip other punctuation.
  2. Remove any ':tag' suffix (e.g. ':main', ':v1') from the id; tags belong in opts, not the id.
  3. Collapse multiple slashes and trim leading/trailing slashes so no empty segments remain.
  4. Reject ids containing '..' or '.' segments at the source (UI validation) before reaching the extension.

Example fix

// before
await provider.import('org/model:main', opts) // throws - colon
// after
const slug = 'org/model-main'.replace(/[^a-zA-Z0-9/_\-.]/g, '-').replace(/\/+/g, '/')
if (/^[a-zA-Z0-9/_\-.]+$/.test(slug) && slug.split('/').every(s => s && s !== '.' && s !== '..')) {
  await provider.import(slug, opts)
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate modelId with the same rules the extension enforces
function isValidModelId(id: string): boolean {
  if (!/^[a-zA-Z0-9/_\-.]+$/.test(id)) return false
  return id.split('/').every(s => s !== '' && s !== '.' && s !== '..')
}
if (!isValidModelId(modelId)) throw new Error(`modelId '${modelId}' rejected by client-side validation`)

Type guard

function isValidModelId(id: string): boolean {
  if (!/^[a-zA-Z0-9/_\-.]+$/.test(id)) return false
  return id.split('/').every(s => s !== '' && s !== '.' && s !== '..')
}

Try / catch

try { await provider.import(modelId, opts) }
catch (e) {
  if (/Invalid modelId/.test(String(e))) { modelId = slugify(modelId); await provider.import(modelId, opts) }
  else throw e
}

Prevention

When it happens

Trigger: Passing a modelId with spaces, colons, or unicode (e.g. 'my model: v2'); passing an absolute or relative path ('../evil', '/etc/x'); passing '//double' or trailing '/' producing empty segments ('foo/'); passing 'a/../b' or './x'; passing a URL as the modelId.

Common situations: User pastes a HuggingFace repo id with a colon tag (org/model:main). UI fails to slugify the id. Auto-generated id from a filename containing spaces or parentheses. Security-sensitive: malicious or accidental path traversal that would write model.yml outside the models directory.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/017da59fd7f966a6. Report an issue: GitHub.