Skip to content

Commit fcfd9ac

Browse files
committed
fix: address CodeRabbit review findings
- wellknown.ts: drop multiline flag from frontmatter regex, extract skill name from URL path segments instead of basename on full URL - agents-md.ts: wrap init/sync in try-catch for user-friendly error messages - publish.ts: add path-traversal guard to mintlify branch - validate.ts: remove duplicate error/warning output in spec report - extractor.ts: add 30s AbortSignal timeout to all fetch calls - skill-generator.ts: fix .filter(Boolean) stripping blank line after frontmatter
1 parent 1becfda commit fcfd9ac

File tree

6 files changed

+62
-49
lines changed

6 files changed

+62
-49
lines changed

packages/cli/src/commands/agents-md.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,21 @@ export class AgentsMdInitCommand extends Command {
4444
return 1;
4545
}
4646

47-
const generator = new AgentsMdGenerator({ projectPath });
48-
const result = generator.generate();
47+
try {
48+
const generator = new AgentsMdGenerator({ projectPath });
49+
const result = generator.generate();
4950

50-
console.log(chalk.dim('Preview:'));
51-
console.log('');
52-
console.log(result.content);
51+
console.log(chalk.dim('Preview:'));
52+
console.log('');
53+
console.log(result.content);
5354

54-
writeFileSync(agentsPath, result.content, 'utf-8');
55-
console.log(chalk.green(`Created ${agentsPath}`));
56-
57-
return 0;
55+
writeFileSync(agentsPath, result.content, 'utf-8');
56+
console.log(chalk.green(`Created ${agentsPath}`));
57+
return 0;
58+
} catch (err) {
59+
console.log(chalk.red(`Failed to generate AGENTS.md: ${err instanceof Error ? err.message : String(err)}`));
60+
return 1;
61+
}
5862
}
5963
}
6064

@@ -74,23 +78,27 @@ export class AgentsMdSyncCommand extends Command {
7478
return 1;
7579
}
7680

77-
const existing = readFileSync(agentsPath, 'utf-8');
78-
const parser = new AgentsMdParser();
79-
80-
if (!parser.hasManagedSections(existing)) {
81-
console.log(chalk.yellow('No managed sections found in AGENTS.md. Nothing to update.'));
82-
return 0;
83-
}
81+
try {
82+
const existing = readFileSync(agentsPath, 'utf-8');
83+
const parser = new AgentsMdParser();
8484

85-
const generator = new AgentsMdGenerator({ projectPath });
86-
const result = generator.generate();
87-
const managedSections = result.sections.filter(s => s.managed);
88-
const updated = parser.updateManagedSections(existing, managedSections);
85+
if (!parser.hasManagedSections(existing)) {
86+
console.log(chalk.yellow('No managed sections found in AGENTS.md. Nothing to update.'));
87+
return 0;
88+
}
8989

90-
writeFileSync(agentsPath, updated, 'utf-8');
91-
console.log(chalk.green(`Updated ${managedSections.length} managed section(s) in AGENTS.md`));
90+
const generator = new AgentsMdGenerator({ projectPath });
91+
const result = generator.generate();
92+
const managedSections = result.sections.filter(s => s.managed);
93+
const updated = parser.updateManagedSections(existing, managedSections);
9294

93-
return 0;
95+
writeFileSync(agentsPath, updated, 'utf-8');
96+
console.log(chalk.green(`Updated ${managedSections.length} managed section(s) in AGENTS.md`));
97+
return 0;
98+
} catch (err) {
99+
console.log(chalk.red(`Failed to sync AGENTS.md: ${err instanceof Error ? err.message : String(err)}`));
100+
return 1;
101+
}
94102
}
95103
}
96104

packages/cli/src/commands/publish.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,14 @@ export class PublishCommand extends Command {
129129
return 0;
130130
}
131131

132+
const resolvedOutput = resolve(outputDir);
132133
for (const skill of validSkills) {
133134
const mintlifyDir = join(outputDir, '.well-known', 'skills', skill.safeName);
135+
const resolvedDir = resolve(mintlifyDir);
136+
if (!resolvedDir.startsWith(resolvedOutput)) {
137+
console.log(chalk.red(`Skipping ${skill.safeName} (path traversal detected)`));
138+
continue;
139+
}
134140
mkdirSync(mintlifyDir, { recursive: true });
135141
const skillMdPath = join(skill.path, 'SKILL.md');
136142
if (existsSync(skillMdPath)) {

packages/cli/src/commands/validate.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,6 @@ function printSpecReport(specResult: SpecValidationResult): void {
158158
}
159159
}
160160

161-
if (specResult.errors.length > 0) {
162-
console.log('');
163-
for (const err of specResult.errors) {
164-
console.log(` ${colors.error(symbols.error)} ${err}`);
165-
}
166-
}
167-
168-
if (specResult.warnings.length > 0) {
169-
console.log('');
170-
for (const w of specResult.warnings) {
171-
console.log(` ${colors.warning(symbols.warning)} ${w}`);
172-
}
173-
}
174161
}
175162

176163
function getOrdinal(n: number): string {

packages/core/src/providers/wellknown.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ import type { GitProviderAdapter, CloneOptions } from './base.js';
66
import { isGitUrl, isLocalPath } from './base.js';
77
import type { GitProvider, CloneResult } from '../types.js';
88

9+
function skillNameFromUrl(url: string): string {
10+
try {
11+
const parsed = new URL(url);
12+
const segments = parsed.pathname.split('/').filter(Boolean);
13+
return segments[segments.length - 1] || parsed.hostname.split('.')[0] || 'default';
14+
} catch {
15+
return basename(url) || 'default';
16+
}
17+
}
18+
19+
const FRONTMATTER_REGEX = /^---\s*\n[\s\S]*?name:\s*.+/;
20+
921
function sanitizeSkillName(name: string): string | null {
1022
if (!name || typeof name !== 'string') return null;
1123
const base = basename(name);
@@ -85,8 +97,8 @@ export class WellKnownProvider implements GitProviderAdapter {
8597
const response = await fetch(fullUrl);
8698
if (response.ok) {
8799
const content = await response.text();
88-
if (/^---\s*\n[\s\S]*?name:\s*.+/m.test(content)) {
89-
const skillName = basename(baseUrl) || 'default';
100+
if (FRONTMATTER_REGEX.test(content)) {
101+
const skillName = skillNameFromUrl(baseUrl);
90102
const safeName = sanitizeSkillName(skillName) ?? 'default';
91103
const skillDir = join(tempDir, safeName);
92104
mkdirSync(skillDir, { recursive: true });
@@ -158,8 +170,8 @@ export class WellKnownProvider implements GitProviderAdapter {
158170
const response = await fetch(fullUrl);
159171
if (response.ok) {
160172
const content = await response.text();
161-
if (/^---\s*\n[\s\S]*?name:\s*.+/m.test(content)) {
162-
const skillName = basename(baseUrl) || 'default';
173+
if (FRONTMATTER_REGEX.test(content)) {
174+
const skillName = skillNameFromUrl(baseUrl);
163175
const safeName = sanitizeSkillName(skillName) ?? 'default';
164176
const skillDir = join(tempDir, safeName);
165177
mkdirSync(skillDir, { recursive: true });

packages/core/src/save/extractor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const LANGUAGE_MAP: Record<string, string> = {
4545

4646
const GITHUB_URL_PATTERN = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/;
4747
const GITHUB_RAW_PATTERN = /^https?:\/\/raw\.githubusercontent\.com\//;
48+
const FETCH_TIMEOUT = 30_000;
4849

4950
export class ContentExtractor {
5051
private turndown: TurndownService;
@@ -61,7 +62,7 @@ export class ContentExtractor {
6162
return this.fetchGitHubContent(url, options);
6263
}
6364

64-
const response = await fetch(url);
65+
const response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
6566
if (!response.ok) {
6667
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
6768
}
@@ -149,7 +150,7 @@ export class ContentExtractor {
149150
rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
150151
}
151152

152-
const response = await fetch(rawUrl);
153+
const response = await fetch(rawUrl, { signal: AbortSignal.timeout(FETCH_TIMEOUT) });
153154
if (!response.ok) {
154155
throw new Error(`Failed to fetch GitHub content: ${response.status} ${response.statusText}`);
155156
}

packages/core/src/save/skill-generator.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,18 @@ export class SkillGenerator {
9090
const yamlTags = tags.map((t) => ` - ${t}`).join('\n');
9191
const savedAt = new Date().toISOString();
9292

93-
return [
93+
const lines = [
9494
'---',
9595
`name: ${name}`,
9696
`description: ${this.yamlEscape(description)}`,
97-
tags.length > 0 ? `tags:\n${yamlTags}` : '',
97+
tags.length > 0 ? `tags:\n${yamlTags}` : null,
9898
'metadata:',
99-
source ? ` source: ${source}` : '',
99+
source ? ` source: ${source}` : null,
100100
` savedAt: ${savedAt}`,
101101
'---',
102-
'',
103-
]
104-
.filter(Boolean)
105-
.join('\n');
102+
].filter((l): l is string => l !== null);
103+
104+
return lines.join('\n') + '\n';
106105
}
107106

108107
private yamlEscape(value: string): string {

0 commit comments

Comments
 (0)