fix(skills): enforce per-file and aggregate size caps for imported SKILL.md content
This commit is contained in:
1 parent
c81d72fb9c
commit
8cf6be05bb
3 files changed
+41
-3
No files matched your search
@@ -15,8 +15,11 @@ interface Logger {
|
||||
error: (...args: unknown[]) => void;
|
||||
}
|
||||
|
||||
/** 1 MB — generous upper bound for a single SKILL.md file. */
|
||||
const MAX_SKILL_BYTES = 1024 * 1024;
|
||||
/** 2 MB — generous upper bound for a single SKILL.md (markdown) file. */
|
||||
export const MAX_SKILL_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
/** 32 MB — aggregate upper bound for all SKILL.md content from a single skill pack. */
|
||||
export const MAX_TOTAL_BYTES = 32 * 1024 * 1024;
|
||||
|
||||
/** 10 MB — upper bound for the recursive git tree JSON response. */
|
||||
const MAX_TREE_BYTES = 10 * 1024 * 1024;
|
||||
@@ -149,6 +152,7 @@ export async function fetchGitHubSkills(
|
||||
}
|
||||
|
||||
const skills: GitHubSkillData[] = [];
|
||||
let totalBytes = 0;
|
||||
|
||||
for (const skillMdPath of skillMdPaths) {
|
||||
try {
|
||||
@@ -159,7 +163,20 @@ export async function fetchGitHubSkills(
|
||||
logger?.warn(`Failed to download ${rawUrl}: ${contentResponse.status}`);
|
||||
continue;
|
||||
}
|
||||
const markdown = await readResponseWithLimit(contentResponse, MAX_SKILL_BYTES);
|
||||
const remainingBudget = MAX_TOTAL_BYTES - totalBytes;
|
||||
const perFileCap = Math.min(MAX_SKILL_BYTES, Math.max(0, remainingBudget));
|
||||
if (perFileCap === 0) {
|
||||
throw new Error(
|
||||
`Skill pack exceeded aggregate limit of ${MAX_TOTAL_BYTES} bytes`,
|
||||
);
|
||||
}
|
||||
const markdown = await readResponseWithLimit(contentResponse, perFileCap);
|
||||
totalBytes += Buffer.byteLength(markdown, "utf-8");
|
||||
if (totalBytes > MAX_TOTAL_BYTES) {
|
||||
throw new Error(
|
||||
`Skill pack exceeded aggregate limit of ${MAX_TOTAL_BYTES} bytes`,
|
||||
);
|
||||
}
|
||||
|
||||
// Collect sibling files in the same directory (or subdirectories)
|
||||
const skillDir =
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
import {
|
||||
resolveGitHubCommitSha,
|
||||
fetchGitHubSkills,
|
||||
MAX_SKILL_BYTES,
|
||||
MAX_TOTAL_BYTES,
|
||||
} from "../skill-github.js";
|
||||
|
||||
interface GitHubImportDeps {
|
||||
@@ -49,6 +51,11 @@ export function createGitHubImporter({ db, logger, skillFilesDir }: GitHubImport
|
||||
markdown: string,
|
||||
_inventory: FileInventoryEntry[],
|
||||
): Promise<string> {
|
||||
if (markdown.length * 2 > MAX_SKILL_BYTES) {
|
||||
throw new Error(
|
||||
`SKILL.md exceeds ${MAX_SKILL_BYTES}-byte limit`,
|
||||
);
|
||||
}
|
||||
const dir = skillFilesDir(companyId, slug);
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(join(dir, "SKILL.md"), markdown, "utf-8");
|
||||
@@ -83,8 +90,16 @@ export function createGitHubImporter({ db, logger, skillFilesDir }: GitHubImport
|
||||
const results: SkillImportResult[] = [];
|
||||
const sourceType: SkillSourceType = originalSkillsShUrl ? "skills_sh" : "github";
|
||||
const nowTs = new Date().toISOString();
|
||||
let totalMaterializedBytes = 0;
|
||||
|
||||
for (const ghSkill of githubSkills) {
|
||||
const skillBytes = Buffer.byteLength(ghSkill.markdown, "utf-8");
|
||||
totalMaterializedBytes += skillBytes;
|
||||
if (totalMaterializedBytes > MAX_TOTAL_BYTES) {
|
||||
throw new Error(
|
||||
`Skill pack exceeded aggregate limit of ${MAX_TOTAL_BYTES} bytes`,
|
||||
);
|
||||
}
|
||||
const parsed = parseFrontmatterMarkdown(ghSkill.markdown);
|
||||
const rawSlug =
|
||||
normalizeSkillSlug(parsed.frontmatter.slug as string | undefined) ??
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
parseSkillImportSource,
|
||||
} from "../skill-keys.js";
|
||||
import { createGitHubImporter } from "./import-github";
|
||||
import { MAX_SKILL_BYTES } from "../skill-github.js";
|
||||
|
||||
const BLOCKED_PATH_PREFIXES_UNIX = ["/etc", "/var", "/proc", "/sys", "/dev", "/boot", "/sbin", "/usr/sbin"];
|
||||
const BLOCKED_PATH_PREFIXES_WIN = ["C:\\Windows", "C:\\Program Files", "C:\\ProgramData"];
|
||||
@@ -63,6 +64,11 @@ export function createSkillImporter({ db, profileDir: _profileDir, logger, skill
|
||||
markdown: string,
|
||||
_inventory: FileInventoryEntry[],
|
||||
): Promise<string> {
|
||||
if (markdown.length * 2 > MAX_SKILL_BYTES) {
|
||||
throw new Error(
|
||||
`SKILL.md exceeds ${MAX_SKILL_BYTES}-byte limit`,
|
||||
);
|
||||
}
|
||||
const dir = skillFilesDir(companyId, slug);
|
||||
await mkdir(dir, { recursive: true });
|
||||
await writeFile(join(dir, "SKILL.md"), markdown, "utf-8");
|
||||
|
||||
Reference in new issue
Block a user