@miphamai/cli 0.77.1 → 0.77.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.77.1",
3
+ "version": "0.77.2",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -9,7 +9,7 @@
9
9
  export const PACKAGE_NAME = '@miphamai/cli' as const
10
10
 
11
11
  /** 当前发布版本 */
12
- export const PACKAGE_VERSION = '0.77.1' as const
12
+ export const PACKAGE_VERSION = '0.77.2' as const
13
13
 
14
14
  /** npm install 全局安装命令 */
15
15
  export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
@@ -53,6 +53,17 @@ export function isValidMarketplaceRef(owner: string, repo: string): boolean {
53
53
  return valid(owner) && valid(repo)
54
54
  }
55
55
 
56
+ /**
57
+ * A skill name is used as a filename segment when installing from a marketplace.
58
+ * It must be a single safe segment: start with an alphanumeric and contain only
59
+ * alphanumerics, dots, dashes, and underscores — no path separators (`/`, `\`),
60
+ * no leading dot (so `..` cannot traverse), no whitespace. Marketplace frontmatter
61
+ * is untrusted, so this gate fails closed before the name ever touches a path.
62
+ */
63
+ export function isValidSkillName(name: string): boolean {
64
+ return /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name)
65
+ }
66
+
56
67
  /**
57
68
  * Read the marketplace sources from disk. A missing or corrupt file falls back
58
69
  * to the default Anthropic sources.
@@ -17,7 +17,12 @@ import { join } from 'node:path'
17
17
  import { homedir } from 'node:os'
18
18
  import type { MiphamConfig } from '../shared/types.js'
19
19
  import communitySkills from './community-registry.json'
20
- import { readMarketplaces, findSkillInMarketplaces, downloadFile } from './marketplace'
20
+ import {
21
+ readMarketplaces,
22
+ findSkillInMarketplaces,
23
+ downloadFile,
24
+ isValidSkillName,
25
+ } from './marketplace'
21
26
 
22
27
  const SKILLS_DIR = join(homedir(), '.mipham', 'skills')
23
28
 
@@ -221,6 +226,14 @@ async function installFromMarketplace(
221
226
  }
222
227
  }
223
228
 
229
+ if (!isValidSkillName(found.name)) {
230
+ return {
231
+ success: false,
232
+ name: found.name,
233
+ message: `Refusing to install skill with unsafe name "${found.name}": skill names must be a single safe filename segment (letters, digits, dots, dashes, underscores).`,
234
+ }
235
+ }
236
+
224
237
  const destPath = join(SKILLS_DIR, `${found.name}.SKILL.md`)
225
238
  if (existsSync(destPath)) {
226
239
  return {
@@ -279,6 +292,14 @@ export async function installSkillFromUrl(
279
292
  .pop()
280
293
  ?.replace(/\.(SKILL\.)?md$/i, '') || 'custom-skill'
281
294
 
295
+ if (!isValidSkillName(name)) {
296
+ return {
297
+ success: false,
298
+ name,
299
+ message: `Refusing to install from URL with unsafe name "${name}": skill names must be a single safe filename segment (letters, digits, dots, dashes, underscores).`,
300
+ }
301
+ }
302
+
282
303
  const destDir = join(SKILLS_DIR)
283
304
  const destPath = join(destDir, `${name}.SKILL.md`)
284
305
 
@@ -322,6 +343,14 @@ export function listInstalledSkills(): string[] {
322
343
  * Remove an installed skill.
323
344
  */
324
345
  export function removeSkill(skillName: string): InstallResult {
346
+ if (!isValidSkillName(skillName)) {
347
+ return {
348
+ success: false,
349
+ name: skillName,
350
+ message: `Refusing to remove skill with unsafe name "${skillName}": skill names must be a single safe filename segment (letters, digits, dots, dashes, underscores).`,
351
+ }
352
+ }
353
+
325
354
  const destPath = join(SKILLS_DIR, `${skillName}.SKILL.md`)
326
355
  if (!existsSync(destPath)) {
327
356
  return { success: false, name: skillName, message: `Skill "${skillName}" is not installed.` }
@@ -320,7 +320,7 @@ export function createBashTool(credentialConfig?: CredentialMaskingConfig): Tool
320
320
  command: { type: 'string', description: 'The bash command to execute' },
321
321
  description: {
322
322
  type: 'string',
323
- description: 'What this command does (for audit log)',
323
+ description: 'What this command does, in plain words (do not echo the command itself)',
324
324
  },
325
325
  timeout: {
326
326
  type: 'integer',