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 fileView on GitHub (pinned to fad3f12a14)
Solutions
- Sanitize modelId to only [a-zA-Z0-9/_-.] before calling import - replace spaces with hyphens, strip other punctuation.
- Remove any ':tag' suffix (e.g. ':main', ':v1') from the id; tags belong in opts, not the id.
- Collapse multiple slashes and trim leading/trailing slashes so no empty segments remain.
- 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
- Slugify user-entered ids at the UI layer (strip colons/tags, replace spaces).
- Block ':tag' suffixes from ever reaching import.
- Run the same regex the extension uses as a form validator.
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
- Invalid modelId: ${modelId}. Only alphanumeric and / _ - . c
- Model ${modelId} already exists
- File not found: ${path}
- Invalid GGUF file(s): ${error.message || 'File format valida
- Model with ID ${model.id} already exists
AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12).
Data as JSON: /api/errors/017da59fd7f966a6.
Report an issue: GitHub.