mastra-ai/mastra · warning · HTTPException
skillPath must be within the allowed directory: ${allowedBas
Error message
skillPath must be within the allowed directory: ${allowedBase} What it means
A 400 thrown to prevent path traversal: the handler resolves `skillPath` to an absolute path and rejects it unless it lies under `SKILLS_BASE_DIR` (or the server's cwd when unset). Publishing skills must only read directories the server operator allows.
Source
Thrown at packages/server/src/server/handlers/stored-skills.ts:615
}
assertStoredResourceScope(existing, await getStoredResourceScope(mastra, requestContext));
// Throws 404 if the caller isn't the owner, admin, or `stored-skills:write[:<id>]` holder.
assertWriteAccess({
requestContext,
resource: 'stored-skills',
resourceId: storedSkillId,
action: 'edit',
record: existing,
});
// Validate skillPath to prevent path traversal
const path = await import('node:path');
const fs = await import('node:fs/promises');
const resolvedPath = path.default.resolve(skillPath);
const allowedBase = path.default.resolve(process.env.SKILLS_BASE_DIR || process.cwd());
if (!resolvedPath.startsWith(allowedBase + path.default.sep) && resolvedPath !== allowedBase) {
throw new HTTPException(400, {
message: `skillPath must be within the allowed directory: ${allowedBase}`,
});
}
// Verify the source directory exists and contains a SKILL.md before attempting
// to publish, so callers get a 400 with context instead of a raw 500/ENOENT.
try {
const stat = await fs.stat(resolvedPath);
if (!stat.isDirectory()) {
throw new HTTPException(400, { message: `skillPath is not a directory: ${resolvedPath}` });
}
} catch (err) {
if (err instanceof HTTPException) throw err;
if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {
throw new HTTPException(400, {
message: `skillPath does not exist on the server filesystem: ${resolvedPath}. Create the skill directory (with a SKILL.md) before publishing, or use a skill that was materialized to disk.`,
});
}View on GitHub (pinned to 75dd419e61)
Solutions
- Set `SKILLS_BASE_DIR` on the server to the directory containing publishable skills and place the skill folder under it
- Pass a path inside the allowed base (or a relative path that resolves under it)
- Compare the resolved path against `path.resolve(process.env.SKILLS_BASE_DIR || process.cwd())` locally before calling
- Avoid `..` segments and cross-directory references in skillPath
Example fix
// before
await publishSkill({ skillPath: '/Users/me/skills/my-skill' });
// after
await publishSkill({ skillPath: '/srv/skills/my-skill' }); // server: SKILLS_BASE_DIR=/srv/skills Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path';
const allowedBase = path.resolve(process.env.SKILLS_BASE_DIR || process.cwd());
const resolved = path.resolve(skillPath);
if (!resolved.startsWith(allowedBase + path.sep) && resolved !== allowedBase) {
throw new Error(`skillPath must be inside ${allowedBase}`);
} Try / catch
try { await publishStoredSkill({ skillPath }); } catch (e) {
if (e.status === 400 && /allowed directory/.test(e.message)) throw new Error(`Move the skill under the server's allowed base: ${e.message}`);
throw e;
} Prevention
- Set SKILLS_BASE_DIR explicitly on the server and keep skills under it
- Always resolve paths (path.resolve) before comparing — never string-prefix raw input
- Reject or normalize `..` segments early in client code
- Remember paths are evaluated on the SERVER, not the caller's machine
When it happens
Trigger: Passing an absolute path outside the allowed base (e.g. `/etc`, `/home/user/other-project`), or a relative path with `../` segments that resolves outside the base, in the publish-skill request.
Common situations: Developer passes their local skill folder path to a remote server where the base dir differs; SKILLS_BASE_DIR unset so the base is the server's cwd, not the expected skills root; symlinks/relative paths resolving unexpectedly.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Invalid route path: "${path}". Path cannot contain '..', '?'
- Worker ${label} must stay within the deployed artifact root.
- ${label} escapes workspace
- Path escapes workspace
- Invalid resourceId: ${resourceId}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/b3b65b613e476202.
Report an issue: GitHub.