Files
gstack/scripts/discover-skills.ts
T
Arun Kumar Thiagarajan c372fd1835 fix(security): skip hidden directories in skill template discovery
discoverTemplates() scans subdirectories for SKILL.md.tmpl files but
only skips node_modules, .git, and dist. Hidden directories like
.claude/, .agents/, and .codex/ (which contain symlinked skill
installs) were being scanned, allowing a malicious .tmpl in a
symlinked skill to inject into the generation pipeline.

Fix: add !d.name.startsWith('.') to the subdirs() filter. This skips
all dot-prefixed directories, matching the standard convention that
hidden dirs are not source code.
2026-03-24 13:35:27 +05:30

40 lines
1.2 KiB
TypeScript

/**
* Shared discovery for SKILL.md and .tmpl files.
* Scans root + one level of subdirs, skipping node_modules/.git/dist.
*/
import * as fs from 'fs';
import * as path from 'path';
const SKIP = new Set(['node_modules', '.git', 'dist']);
function subdirs(root: string): string[] {
return fs.readdirSync(root, { withFileTypes: true })
.filter(d => d.isDirectory() && !d.name.startsWith('.') && !SKIP.has(d.name))
.map(d => d.name);
}
export function discoverTemplates(root: string): Array<{ tmpl: string; output: string }> {
const dirs = ['', ...subdirs(root)];
const results: Array<{ tmpl: string; output: string }> = [];
for (const dir of dirs) {
const rel = dir ? `${dir}/SKILL.md.tmpl` : 'SKILL.md.tmpl';
if (fs.existsSync(path.join(root, rel))) {
results.push({ tmpl: rel, output: rel.replace(/\.tmpl$/, '') });
}
}
return results;
}
export function discoverSkillFiles(root: string): string[] {
const dirs = ['', ...subdirs(root)];
const results: string[] = [];
for (const dir of dirs) {
const rel = dir ? `${dir}/SKILL.md` : 'SKILL.md';
if (fs.existsSync(path.join(root, rel))) {
results.push(rel);
}
}
return results;
}