@almyty/skills 1.0.16 → 1.2.0

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/README.md CHANGED
@@ -116,10 +116,14 @@ Create `.almytyrc` in your project or home directory:
116
116
 
117
117
  Requires `npx @almyty/auth login` first. Reads credentials from `~/.almyty/credentials.json`.
118
118
 
119
- ## Docs
119
+ ## About almyty
120
120
 
121
- https://almyty.com/docs
121
+ almyty is the full-stack platform for AI agents, agnostic by design: any LLM, any
122
+ API turned into tools, served over MCP, A2A, UTCP, and Agent Skills. Open source,
123
+ no lock-in.
122
124
 
123
- ## License
125
+ - Website — https://almyty.com
126
+ - Docs — https://docs.almyty.com
127
+ - Source — https://github.com/almyty-inc/almyty
124
128
 
125
- BSL-1.1
129
+ Apache-2.0 © Almyty Inc.
package/dist/installer.js CHANGED
@@ -10,12 +10,36 @@
10
10
  * already present in every SKILL.md we generate.
11
11
  */
12
12
  import { writeFileSync, mkdirSync, existsSync, readdirSync, rmSync, readFileSync } from 'fs';
13
- import { join } from 'path';
13
+ import { join, resolve, sep } from 'path';
14
14
  /** Legacy prefix from <=v1.0.9 — older installs still use this dir
15
15
  * name. Kept here so `remove`/`installed` continue to find them. */
16
16
  const LEGACY_SKILL_PREFIX = 'almyty-';
17
17
  /** Marker line we expect inside every SKILL.md frontmatter we wrote. */
18
18
  const ALMYTY_MARKER = /^\s*author:\s*almyty\s*$/m;
19
+ /**
20
+ * Skill names come from the backend/gateway and are used to build
21
+ * filesystem paths under skillsDir. A malicious or compromised gateway
22
+ * could return `name: "../../../.ssh/authorized_keys"` and have the
23
+ * installer write — or, via the legacy-dir cleanup, recursively delete —
24
+ * arbitrary files on the user's machine (and the daemon/watch modes do
25
+ * this automatically on a timer). Restrict names to a single safe path
26
+ * segment.
27
+ */
28
+ const SAFE_SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
29
+ function isSafeSkillName(name) {
30
+ return (typeof name === 'string' &&
31
+ name !== '.' &&
32
+ name !== '..' &&
33
+ !name.includes('/') &&
34
+ !name.includes('\\') &&
35
+ SAFE_SKILL_NAME.test(name));
36
+ }
37
+ /** Defence in depth: assert a built path stays inside skillsDir. */
38
+ function isInside(baseDir, candidate) {
39
+ const base = resolve(baseDir);
40
+ const target = resolve(candidate);
41
+ return target === base || target.startsWith(base + sep);
42
+ }
19
43
  /**
20
44
  * Strip the legacy `name: almyty-<x>` line in the SKILL.md frontmatter
21
45
  * to `name: <x>` so the agent-visible identifier is the skill's own
@@ -51,6 +75,13 @@ export function installSkills(skills, target) {
51
75
  const files = [];
52
76
  mkdirSync(target.skillsDir, { recursive: true });
53
77
  for (const skill of skills) {
78
+ // Reject backend-supplied names that aren't a single safe path
79
+ // segment — skip rather than throw so one bad entry can't break the
80
+ // whole install (e.g. in daemon/watch mode).
81
+ if (!isSafeSkillName(skill.name)) {
82
+ console.warn(`Skipping skill with unsafe name: ${JSON.stringify(skill.name)}`);
83
+ continue;
84
+ }
54
85
  const dirName = skill.name;
55
86
  const skillDir = join(target.skillsDir, dirName);
56
87
  const skillFile = join(skillDir, 'SKILL.md');
@@ -58,6 +89,11 @@ export function installSkills(skills, target) {
58
89
  // shape, remove it before writing the new shape so the agent
59
90
  // doesn't see two copies of the same skill.
60
91
  const legacyDir = join(target.skillsDir, `${LEGACY_SKILL_PREFIX}${skill.name}`);
92
+ // Containment guard (defence in depth) before any write/delete.
93
+ if (!isInside(target.skillsDir, skillDir) || !isInside(target.skillsDir, legacyDir)) {
94
+ console.warn(`Skipping skill whose path escapes the skills directory: ${skill.name}`);
95
+ continue;
96
+ }
61
97
  if (legacyDir !== skillDir && existsSync(legacyDir)) {
62
98
  rmSync(legacyDir, { recursive: true, force: true });
63
99
  }
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@almyty/skills",
3
- "version": "1.0.16",
4
- "description": "Install API skills into AI coding agents — Claude Code, Cursor, Windsurf, Copilot, Codex",
3
+ "version": "1.2.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Install any API as a skill into Claude Code, Cursor, Windsurf, Copilot, or Codex \u2014 almyty turns OpenAPI and GraphQL into agent-ready skills.",
5
8
  "type": "module",
6
9
  "main": "dist/index.js",
7
10
  "bin": {
@@ -30,9 +33,9 @@
30
33
  "almyty"
31
34
  ],
32
35
  "author": "almyty",
33
- "license": "BSL-1.1",
36
+ "license": "Apache-2.0",
34
37
  "dependencies": {
35
- "@almyty/client": "^0.1.0",
38
+ "@almyty/client": "^1.2.0",
36
39
  "@clack/prompts": "^1.2.0"
37
40
  },
38
41
  "devDependencies": {
@@ -40,5 +43,14 @@
40
43
  "tsx": "^4.7.0",
41
44
  "typescript": "^5.3.0",
42
45
  "vitest": "^4.1.0"
46
+ },
47
+ "homepage": "https://almyty.com",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/almyty-inc/almyty.git",
51
+ "directory": "packages/skills-cli"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/almyty-inc/almyty/issues"
43
55
  }
44
56
  }