@studiomeyer-io/skilldoctor 0.1.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/LICENSE +21 -0
- package/README.md +187 -0
- package/SECURITY.md +53 -0
- package/dist/cli.cjs +1718 -0
- package/dist/cli.d.cts +31 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.js +1715 -0
- package/dist/index.cjs +1569 -0
- package/dist/index.d.cts +220 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.js +1540 -0
- package/dist/types-lUfaWSNG.d.cts +117 -0
- package/dist/types-lUfaWSNG.d.ts +117 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StudioMeyer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# skilldoctor
|
|
2
|
+
|
|
3
|
+
**A linter and security scanner for AI-agent skill & instruction files.** Think `eslint`, but for the `SKILL.md`, `AGENTS.md`, and subagent files that agents now install like packages.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/studiomeyer-io/skilldoctor/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/skilldoctor)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @studiomeyer-io/skilldoctor check .claude/skills
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
my-skill/SKILL.md [skill] F (0/100)
|
|
15
|
+
2:1 ✖ error `name` "My_Skill" is invalid. Use 1-64 lowercase chars … skill/invalid-name
|
|
16
|
+
6:1 ✖ error Contains "ignore previous instructions"-style injection. sec/prompt-injection
|
|
17
|
+
7:11 ✖ error Outbound network call near secret/env values — possible … sec/data-exfiltration
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Why this exists
|
|
23
|
+
|
|
24
|
+
In 2026, "skills" exploded as the way to extend coding agents. Claude Code reads `SKILL.md` files; **`AGENTS.md`** became a cross-tool convention (adopted by Cursor, Codex, Gemini CLI, Copilot, and [dozens of other agents](https://agentskills.io/clients)); subagents carry their own YAML frontmatter. The [Agent Skills](https://agentskills.io) format is now an open standard.
|
|
25
|
+
|
|
26
|
+
These files are **shared and installed like packages** — copied from a gist, cloned from a repo, pulled from a marketplace. That creates two gaps with no off-the-shelf tooling:
|
|
27
|
+
|
|
28
|
+
1. **No linter.** Nothing validates the frontmatter, catches an over-broad `tools:` grant, or flags a missing/vague `description` that makes a skill invisible to the agent.
|
|
29
|
+
2. **No security scan.** A skill's *body is a prompt that the agent will follow*. A malicious or careless skill can hide prompt-injection text, a `curl … $(env)` exfiltration line, or a "disable the approval prompt" instruction inside what looks like a helpful workflow. This is a real supply-chain surface — NVIDIA shipped a research scanner ("SkillSpector") for exactly this class of risk, and the broader prompt-injection/agent supply-chain problem is well documented (e.g. [Simon Willison on prompt injection](https://simonwillison.net/series/prompt-injection/), the [OWASP LLM Top 10](https://genai.owasp.org/)).
|
|
30
|
+
|
|
31
|
+
Tools that **sync/install** skills exist. A **linter + security scanner** for them did not. `skilldoctor` is that tool.
|
|
32
|
+
|
|
33
|
+
> **Honest disclaimer — heuristic, not a sandbox.** skilldoctor reads text and matches patterns. It **never executes, fetches, or evaluates anything**. It will have false positives (a legit skill that documents `curl`) and false negatives (novel obfuscation). It is a fast first line of defense and a CI gate — **not** a guarantee that an installed skill is safe. Always read skills before trusting them.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# one-off
|
|
41
|
+
npx @studiomeyer-io/skilldoctor check <path>
|
|
42
|
+
|
|
43
|
+
# or add to a project
|
|
44
|
+
npm install --save-dev @studiomeyer-io/skilldoctor
|
|
45
|
+
# (the installed command is `skilldoctor`)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Requires Node.js ≥ 20. Heuristic-only by default — **no API key needed**.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
skilldoctor check <path-or-glob...> [options]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`<path-or-glob>` can be a file, a directory (scanned recursively for `SKILL.md`, `AGENTS.md`, and `agents/*.md`), or a glob like `"**/SKILL.md"`.
|
|
57
|
+
|
|
58
|
+
| Option | Description |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `--json <file>` | Write a machine-readable JSON report. |
|
|
61
|
+
| `--sarif <file>` | Write a SARIF 2.1.0 report (for GitHub code scanning). |
|
|
62
|
+
| `--fix` | Apply mechanical fixes in place. **Frontmatter only — never the body.** |
|
|
63
|
+
| `--fail-on <sev>` | Exit non-zero if any finding is at/above `error` \| `warning` \| `info`. Default `error`. |
|
|
64
|
+
| `--no-color` | Disable ANSI colors (also respects `NO_COLOR`). |
|
|
65
|
+
| `--quiet` | Suppress the terminal report (still writes `--json`/`--sarif`). |
|
|
66
|
+
| `-h, --help` / `-v, --version` | Help / version. |
|
|
67
|
+
|
|
68
|
+
**Exit codes:** `0` clean (relative to `--fail-on`), `1` findings at/above threshold, `2` usage error / no files found.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
skilldoctor check .claude/skills --fail-on warning
|
|
72
|
+
skilldoctor check "**/SKILL.md" --sarif results.sarif
|
|
73
|
+
skilldoctor check AGENTS.md --json report.json
|
|
74
|
+
skilldoctor check ./skills --fix
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## What it checks
|
|
78
|
+
|
|
79
|
+
skilldoctor validates against the **base [Agent Skills spec](https://agentskills.io/specification)** strictly, **recognizes** the documented Claude Code extension fields (so they are never mis-flagged), and treats genuinely unknown fields **leniently** (an `info`, not an error) — because clients are free to add their own metadata and the tool should not invent rules.
|
|
80
|
+
|
|
81
|
+
It understands three file kinds:
|
|
82
|
+
|
|
83
|
+
- **`SKILL.md`** — Agent Skills / Claude Code skill (frontmatter `name` + `description`, optional `allowed-tools`, …).
|
|
84
|
+
- **subagent** — a `.md` in an `agents/` directory (frontmatter `name`, `description`, `tools`, `model`, …).
|
|
85
|
+
- **`AGENTS.md`** — plain markdown, no frontmatter required; only content/security checks apply.
|
|
86
|
+
|
|
87
|
+
### Lint rules (17)
|
|
88
|
+
|
|
89
|
+
| Rule | Default severity | Fixable | What it checks |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| `skill/missing-name` | error | no | `name` is required. |
|
|
92
|
+
| `skill/invalid-name` | error | no | `name` must be 1-64 lowercase chars (`a-z 0-9 -`), no leading/trailing/consecutive hyphens. |
|
|
93
|
+
| `skill/name-dir-mismatch` | warning | no | `name` must match the parent directory (spec). |
|
|
94
|
+
| `skill/missing-description` | error | yes | `description` is required (it's how agents decide when to load a skill). |
|
|
95
|
+
| `skill/empty-description` | error | yes | `description` is blank. |
|
|
96
|
+
| `skill/description-too-short` | warning | no | Too short to convey what/when. |
|
|
97
|
+
| `skill/description-too-long` | warning | no | Over the 1024-char spec limit. |
|
|
98
|
+
| `skill/vague-description` | info | no | Generic phrasing with no trigger keywords. |
|
|
99
|
+
| `skill/empty-body` | warning | no | Instruction body is empty. |
|
|
100
|
+
| `skill/frontmatter-schema` | error | no | YAML unparseable / not a mapping / wrong field type. |
|
|
101
|
+
| `skill/unknown-field` | info | no | Field not in the spec or known extensions (lenient). |
|
|
102
|
+
| `skill/duplicate-key` | warning | no | A frontmatter key appears twice (YAML keeps the last). |
|
|
103
|
+
| `skill/trailing-whitespace` | info | yes | Trailing whitespace in frontmatter. |
|
|
104
|
+
| `skill/duplicate-name` | error | no | Two files in the set declare the same `name`. |
|
|
105
|
+
| `tools/wildcard-grant` | warning | no | Bare `*` / `all` grant — least-privilege violation. |
|
|
106
|
+
| `tools/over-broad-for-readonly` | warning | no | Read-only description but write/exec/network tools granted. |
|
|
107
|
+
| `tools/duplicate-tool` | info | yes | Same tool listed twice. |
|
|
108
|
+
|
|
109
|
+
### Security-scan rules (8)
|
|
110
|
+
|
|
111
|
+
Run over the **description + body**, treated as untrusted input:
|
|
112
|
+
|
|
113
|
+
| Rule | Default severity | Fixable | What it detects |
|
|
114
|
+
| --- | --- | --- | --- |
|
|
115
|
+
| `sec/prompt-injection` | error | no | "ignore previous instructions", "disregard your system prompt", role-override/jailbreak personas, injected "new instructions:". |
|
|
116
|
+
| `sec/disable-safety` | error | no | Instructions to disable safety/guardrails/hooks/approval, or `--dangerously-skip-permissions`. |
|
|
117
|
+
| `sec/data-exfiltration` | error | no | An outbound call (curl/POST/fetch to an external URL) **near** secrets/env — the exfil shape. |
|
|
118
|
+
| `sec/env-base64` | warning | no | base64/encode of `env`/secrets (covert exfil precursor). |
|
|
119
|
+
| `sec/secret-access` | warning | no | Reads `~/.ssh`, `.aws/credentials`, `.env`, known secret env vars, … |
|
|
120
|
+
| `sec/suspicious-tool-combo` | warning | no | A "read-only/docs" skill that grants **Bash + network** — exfil-enabling combo. |
|
|
121
|
+
| `sec/destructive-command` | warning | no | `rm -rf /`, `curl … \| sh`, `git push --force`, recursive `chmod 777`. |
|
|
122
|
+
| `sec/hidden-unicode` | warning | no | Zero-width / bidirectional control characters that hide text from a human reviewer (Trojan-Source style). |
|
|
123
|
+
|
|
124
|
+
All patterns are regex/heuristic, **ReDoS-safe** (anchored, bounded windows — no catastrophic backtracking; there's a test that throws 100 KB of adversarial input at the scanner and asserts it finishes in well under a second), and **execute nothing**.
|
|
125
|
+
|
|
126
|
+
## Grading
|
|
127
|
+
|
|
128
|
+
Each file gets a `0-100` score and an `A`–`F` grade. Findings deduct points, weighted by category and severity — **security findings weigh far more than lint findings**, so a single hard security hit cannot leave a file with a passing grade. A batch grade is the mean of file scores pulled toward the single worst file, so one dangerous skill in a set can't be averaged away.
|
|
129
|
+
|
|
130
|
+
## CI: GitHub Action
|
|
131
|
+
|
|
132
|
+
Copy [`examples/skilldoctor.yml`](examples/skilldoctor.yml) into `.github/workflows/` to lint your skills on every push and upload findings to GitHub code scanning:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
name: skilldoctor
|
|
136
|
+
on: [push, pull_request]
|
|
137
|
+
permissions:
|
|
138
|
+
contents: read
|
|
139
|
+
security-events: write # required to upload SARIF
|
|
140
|
+
jobs:
|
|
141
|
+
lint-skills:
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
steps:
|
|
144
|
+
- uses: actions/checkout@v4
|
|
145
|
+
- uses: actions/setup-node@v4
|
|
146
|
+
with: { node-version: 20 }
|
|
147
|
+
- run: npx @studiomeyer-io/skilldoctor check ".claude/skills" "**/AGENTS.md" --sarif skilldoctor.sarif --fail-on warning
|
|
148
|
+
- if: always()
|
|
149
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
150
|
+
with:
|
|
151
|
+
sarif_file: skilldoctor.sarif
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Library API
|
|
155
|
+
|
|
156
|
+
skilldoctor ships dual ESM + CJS with TypeScript types.
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { analyzeContent, fixFile, parseFile, sarifString } from "skilldoctor";
|
|
160
|
+
|
|
161
|
+
const report = analyzeContent("my-skill/SKILL.md", contents);
|
|
162
|
+
console.log(report.grade, report.score);
|
|
163
|
+
for (const f of report.findings) {
|
|
164
|
+
console.log(`${f.line}:${f.column} ${f.severity} ${f.ruleId} ${f.message}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// mechanical fixes (frontmatter only)
|
|
168
|
+
const fixed = fixFile(parseFile("my-skill/SKILL.md", contents));
|
|
169
|
+
if (fixed.changed) writeFileSync("my-skill/SKILL.md", fixed.output);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Key exports: `analyzeContent`, `analyzeFiles`, `analyzePaths`, `fixFile`, `parseFile`, `discoverFiles`, `renderTerminal`, `toJsonReport`/`jsonString`, `toSarif`/`sarifString`, `RULES`, and all types.
|
|
173
|
+
|
|
174
|
+
## Sources (formats verified, not invented)
|
|
175
|
+
|
|
176
|
+
skilldoctor's rules are grounded in the actual current specs (checked while building, not from memory):
|
|
177
|
+
|
|
178
|
+
- **Agent Skills standard** — [agentskills.io/specification](https://agentskills.io/specification): `name` (≤64, `^[a-z0-9]+(-[a-z0-9]+)*$`, must match directory), `description` (1-1024, required), `license`, `compatibility` (≤500), `metadata`, `allowed-tools` (space-separated, experimental).
|
|
179
|
+
- **Claude Code skills** — [code.claude.com/docs/en/skills](https://code.claude.com/docs/en/skills): recognizes the extension fields (`when_to_use`, `disable-model-invocation`, `user-invocable`, `disallowed-tools`, `model`, `effort`, `context`, `agent`, `paths`, `shell`, …); combined `description`+`when_to_use` is truncated at 1,536 chars in the skill listing.
|
|
180
|
+
- **Claude Code subagents** — [code.claude.com/docs/en/sub-agents](https://code.claude.com/docs/en/sub-agents): `name` (required, lowercase+hyphens) + `description` (required), `tools` (comma-separated or list, inherits if omitted), `model` (`sonnet`/`opus`/`haiku`/`fable`/full-id/`inherit`).
|
|
181
|
+
- **AGENTS.md** — [agents.md](https://agents.md): "just standard Markdown … no required fields" — so skilldoctor only content/security-checks these.
|
|
182
|
+
|
|
183
|
+
When a field's meaning is uncertain, skilldoctor **warns leniently rather than inventing a hard rule**.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
[MIT](./LICENSE) © 2026 StudioMeyer. See [SECURITY.md](./SECURITY.md) for the security policy and the threat-model boundaries.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
If you find a security issue in skilldoctor itself, please **do not open a public
|
|
6
|
+
issue**. Email the maintainers at **security@studiomeyer.io** (or open a private
|
|
7
|
+
GitHub security advisory). We aim to acknowledge within 72 hours.
|
|
8
|
+
|
|
9
|
+
Please include: affected version, a reproduction (a minimal skill/instruction
|
|
10
|
+
file that triggers the issue), and the impact you observed.
|
|
11
|
+
|
|
12
|
+
## Threat model — what skilldoctor is and is not
|
|
13
|
+
|
|
14
|
+
skilldoctor is a **static, heuristic linter + scanner**. Understanding its
|
|
15
|
+
boundaries is part of using it safely.
|
|
16
|
+
|
|
17
|
+
### What it does
|
|
18
|
+
|
|
19
|
+
- Reads skill / instruction files as **text**.
|
|
20
|
+
- Parses YAML frontmatter and matches documented patterns (lint + security).
|
|
21
|
+
- Emits findings, a grade, JSON, and SARIF.
|
|
22
|
+
|
|
23
|
+
### What it explicitly does NOT do
|
|
24
|
+
|
|
25
|
+
- **It never executes, sources, evals, or imports** any analyzed file.
|
|
26
|
+
- **It never makes network requests** as part of scanning. Heuristic-only by
|
|
27
|
+
default; no API key required.
|
|
28
|
+
- **It is not a sandbox** and not a proof of safety. A clean skilldoctor report
|
|
29
|
+
means "none of our heuristics fired," not "this skill is safe to run."
|
|
30
|
+
|
|
31
|
+
### Known limitations (by design)
|
|
32
|
+
|
|
33
|
+
- **False positives.** A legitimate skill that documents `curl`, `rm`, or quotes
|
|
34
|
+
an injection string as an example may be flagged. Review findings in context.
|
|
35
|
+
- **False negatives.** Novel obfuscation, logic split across referenced files,
|
|
36
|
+
or instructions phrased to evade the patterns can slip through.
|
|
37
|
+
- skilldoctor analyzes the files you point it at. It does **not** follow
|
|
38
|
+
`references/` includes or fetch remote content.
|
|
39
|
+
|
|
40
|
+
### Safe-by-construction properties we maintain
|
|
41
|
+
|
|
42
|
+
- **No code execution** anywhere in the analysis path.
|
|
43
|
+
- **ReDoS resistance.** All scanner regexes are anchored/linear with bounded
|
|
44
|
+
look-windows — no catastrophic backtracking. A regression test feeds the
|
|
45
|
+
scanner large adversarial input and asserts it completes quickly.
|
|
46
|
+
- **`--fix` never rewrites body content.** Auto-fix only touches YAML
|
|
47
|
+
frontmatter in deterministic, idempotent ways, so it can never silently alter
|
|
48
|
+
(or appear to "sanitize") instruction text.
|
|
49
|
+
|
|
50
|
+
## Supported versions
|
|
51
|
+
|
|
52
|
+
The latest published `0.x` release receives security fixes. skilldoctor is
|
|
53
|
+
pre-1.0; APIs and rules may change between minor versions.
|