@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 +8 -4
- package/dist/installer.js +37 -1
- package/package.json +16 -4
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
|
-
##
|
|
119
|
+
## About almyty
|
|
120
120
|
|
|
121
|
-
|
|
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
|
-
|
|
125
|
+
- Website — https://almyty.com
|
|
126
|
+
- Docs — https://docs.almyty.com
|
|
127
|
+
- Source — https://github.com/almyty-inc/almyty
|
|
124
128
|
|
|
125
|
-
|
|
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
|
|
4
|
-
"
|
|
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": "
|
|
36
|
+
"license": "Apache-2.0",
|
|
34
37
|
"dependencies": {
|
|
35
|
-
"@almyty/client": "^
|
|
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
|
}
|