Summary
The Agent Skills spec defines allowed-tools as "a space-separated string of tools that are pre-approved to run". mcp_least_privilege splits the value on commas only, so a spec-compliant value is read as one tool name, maps to no capability, and leaves the skill with nothing declared. LP1 then fires once per capability the code uses. The same skill with commas scans clean.
Cause
_normalize_allowed_tools (src/skillspector/nodes/analyzers/mcp_least_privilege.py:134) handles the list form and the comma-separated string form, and its docstring says so: "Accepts the list form ([Bash, Read]) and the comma-separated string form ("Bash, Read"). Anything else yields an empty list."
if isinstance(value, str):
return [t.strip() for t in value.split(",") if t.strip()]
"Bash Read Edit Write Glob Grep env".split(",") returns a single element. The _TOOL_TO_CAPABILITY.get() lookup at mcp_least_privilege.py:194 misses it, _map_allowed_tools_to_categories returns an empty set, and declared_categories is still empty when LP1 runs at mcp_least_privilege.py:340.
Reproduce
mkdir -p skill/scripts
cat > skill/SKILL.md <<'EOF'
---
name: sep-repro
description: Minimal reproduction for allowed-tools separator parsing.
allowed-tools: Bash Read Edit Write Glob Grep env
---
Run `scripts/probe.py` to collect the report.
EOF
cat > skill/scripts/probe.py <<'EOF'
import os
import subprocess
def run(path):
root = os.environ.get("PROBE_ROOT", ".")
subprocess.run(["echo", root], check=True)
with open(path, "r") as fh:
data = fh.read()
with open(path + ".out", "w") as fh:
fh.write(data)
EOF
skillspector scan skill --no-llm --format json --output space.json
sed -i 's/^allowed-tools:.*/allowed-tools: Bash, Read, Edit, Write, Glob, Grep, env/' skill/SKILL.md
skillspector scan skill --no-llm --format json --output comma.json
Expected: both runs declare the same capabilities, since the two values list the same seven tools.
Actual: space.json reports 4 LP1 findings and score 41 MEDIUM / CAUTION, while comma.json reports 0 and score 17 LOW / SAFE.
The skill uses 'env' capability that is not listed in its permissions.
The skill uses 'file_read' capability that is not listed in its permissions.
The skill uses 'file_write' capability that is not listed in its permissions.
The skill uses 'shell' capability that is not listed in its permissions.
The whole declaration is discarded rather than the one unmapped entry: Bash stops covering shell, Read stops covering file_read, and Write stops covering file_write.
Impact
A skill that declares capabilities the way the spec specifies is reported as under-declared, while the same skill using an off-spec separator passes. On a real skill the swing was larger than in the minimal case above: 48 MEDIUM with commas against 85 CRITICAL / DO_NOT_INSTALL with spaces. Nothing in the output points at the separator, because every finding names a capability rather than a syntax problem, so the natural response is to add declarations that are already present.
In NVIDIA's public skills catalog, 81 of 323 skills set allowed-tools, and 65 of those carry more than one whitespace-separated entry, so their declarations are currently discarded. 4 use the comma form the parser expects.
Related
#300 proposes new remediation wording built on the snippet allowed-tools: Bash(python:*) Read Write. That declares nothing under the current parser, so a user following the corrected remediation still sees LP1 and LP3 fire. #15 added allowed-tools as a recognised declaration source.
Environment
SkillSpector v2.5.1, Python 3.14, Linux.
Summary
The Agent Skills spec defines
allowed-toolsas "a space-separated string of tools that are pre-approved to run".mcp_least_privilegesplits the value on commas only, so a spec-compliant value is read as one tool name, maps to no capability, and leaves the skill with nothing declared. LP1 then fires once per capability the code uses. The same skill with commas scans clean.Cause
_normalize_allowed_tools(src/skillspector/nodes/analyzers/mcp_least_privilege.py:134) handles the list form and the comma-separated string form, and its docstring says so: "Accepts the list form ([Bash, Read]) and the comma-separated string form ("Bash, Read"). Anything else yields an empty list.""Bash Read Edit Write Glob Grep env".split(",")returns a single element. The_TOOL_TO_CAPABILITY.get()lookup atmcp_least_privilege.py:194misses it,_map_allowed_tools_to_categoriesreturns an empty set, anddeclared_categoriesis still empty when LP1 runs atmcp_least_privilege.py:340.Reproduce
Expected: both runs declare the same capabilities, since the two values list the same seven tools.
Actual:
space.jsonreports 4 LP1 findings and score 41 MEDIUM / CAUTION, whilecomma.jsonreports 0 and score 17 LOW / SAFE.The whole declaration is discarded rather than the one unmapped entry:
Bashstops coveringshell,Readstops coveringfile_read, andWritestops coveringfile_write.Impact
A skill that declares capabilities the way the spec specifies is reported as under-declared, while the same skill using an off-spec separator passes. On a real skill the swing was larger than in the minimal case above: 48 MEDIUM with commas against 85 CRITICAL /
DO_NOT_INSTALLwith spaces. Nothing in the output points at the separator, because every finding names a capability rather than a syntax problem, so the natural response is to add declarations that are already present.In NVIDIA's public skills catalog, 81 of 323 skills set
allowed-tools, and 65 of those carry more than one whitespace-separated entry, so their declarations are currently discarded. 4 use the comma form the parser expects.Related
#300 proposes new remediation wording built on the snippet
allowed-tools: Bash(python:*) Read Write. That declares nothing under the current parser, so a user following the corrected remediation still sees LP1 and LP3 fire. #15 addedallowed-toolsas a recognised declaration source.Environment
SkillSpector v2.5.1, Python 3.14, Linux.