mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-08 08:11:38 +00:00
feat(skills): add opt-in AST deep diagnostics
Add opt-in AST diagnostics for skill review without making Skills Guard stricter by default. - Add hermes skills inspect --ast-deep to scan fetched skill bundles before installation - Add hermes skills audit --deep to scan already-installed hub skills - Keep AST analysis in tools/skills_ast_audit.py, separate from tools/skills_guard.py - Label output as diagnostic hints, not security verdicts - Cover dynamic import/access patterns: importlib, __import__(computed), getattr(computed), and __dict__[computed] This follows the maintainer guidance from closed PR #7436: useful AST-level analysis belongs in an opt-in diagnostic path, not in Skills Guard's default heuristic scan.
This commit is contained in:
parent
86871ee25a
commit
7255050c99
5 changed files with 704 additions and 12 deletions
|
|
@ -164,7 +164,7 @@ COMMAND_REGISTRY: list[CommandDef] = [
|
|||
cli_only=True),
|
||||
CommandDef("skills", "Search, install, inspect, or manage skills",
|
||||
"Tools & Skills", cli_only=True,
|
||||
subcommands=("search", "browse", "inspect", "install")),
|
||||
subcommands=("search", "browse", "inspect", "install", "audit")),
|
||||
CommandDef("bundles", "List skill bundles (aliases /<name> for multiple skills)",
|
||||
"Tools & Skills"),
|
||||
CommandDef("cron", "Manage scheduled tasks", "Tools & Skills",
|
||||
|
|
|
|||
|
|
@ -12267,6 +12267,11 @@ Examples:
|
|||
"inspect", help="Preview a skill without installing"
|
||||
)
|
||||
skills_inspect.add_argument("identifier", help="Skill identifier")
|
||||
skills_inspect.add_argument(
|
||||
"--ast-deep",
|
||||
action="store_true",
|
||||
help="Run AST-level diagnostics on Python files before installing",
|
||||
)
|
||||
|
||||
skills_list = skills_subparsers.add_parser("list", help="List installed skills")
|
||||
skills_list.add_argument(
|
||||
|
|
@ -12301,6 +12306,11 @@ Examples:
|
|||
skills_audit.add_argument(
|
||||
"name", nargs="?", help="Specific skill to audit (default: all)"
|
||||
)
|
||||
skills_audit.add_argument(
|
||||
"--deep",
|
||||
action="store_true",
|
||||
help="Run AST-level analysis on Python files (opt-in diagnostic)",
|
||||
)
|
||||
|
||||
skills_uninstall = skills_subparsers.add_parser(
|
||||
"uninstall", help="Remove a hub-installed skill"
|
||||
|
|
|
|||
|
|
@ -630,8 +630,13 @@ def do_install(identifier: str, category: str = "", force: bool = False,
|
|||
c.print("[dim]Use /reset to start a new session now, or --now to activate immediately (invalidates prompt cache).[/]\n")
|
||||
|
||||
|
||||
def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
|
||||
"""Preview a skill's SKILL.md content without installing."""
|
||||
def do_inspect(identifier: str, console: Optional[Console] = None,
|
||||
ast_deep: bool = False) -> None:
|
||||
"""Preview a skill's SKILL.md content without installing.
|
||||
|
||||
When ``ast_deep=True``, also runs AST-level diagnostics against Python
|
||||
files in the fetched bundle before installation.
|
||||
"""
|
||||
from tools.skills_hub import GitHubAuth, create_source_router
|
||||
|
||||
c = console or _console
|
||||
|
|
@ -677,6 +682,11 @@ def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
|
|||
preview += f"\n\n... ({len(lines) - 50} more lines)"
|
||||
c.print(Panel(preview, title="SKILL.md Preview", subtitle="hermes skills install <id> to install"))
|
||||
|
||||
if bundle and ast_deep:
|
||||
from tools.skills_ast_audit import ast_scan_bundle_files, format_ast_report
|
||||
ast_findings = ast_scan_bundle_files(bundle.files)
|
||||
c.print(format_ast_report(ast_findings, skill_name=meta.name))
|
||||
|
||||
c.print()
|
||||
|
||||
|
||||
|
|
@ -906,8 +916,13 @@ def do_update(name: Optional[str] = None, console: Optional[Console] = None) ->
|
|||
c.print(f"[bold green]Updated {len(updates)} skill(s).[/]\n")
|
||||
|
||||
|
||||
def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> None:
|
||||
"""Re-run security scan on installed hub skills."""
|
||||
def do_audit(name: Optional[str] = None, console: Optional[Console] = None,
|
||||
deep: bool = False) -> None:
|
||||
"""Re-run security scan on installed hub skills.
|
||||
|
||||
When ``deep=True``, also runs AST-level analysis on Python files
|
||||
(opt-in diagnostic, not a security gate).
|
||||
"""
|
||||
from tools.skills_hub import HubLockFile, SKILLS_DIR
|
||||
from tools.skills_guard import scan_skill, format_scan_report
|
||||
|
||||
|
|
@ -928,6 +943,10 @@ def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> N
|
|||
|
||||
c.print(f"\n[bold]Auditing {len(targets)} skill(s)...[/]\n")
|
||||
|
||||
ast_scan_skill = format_ast_report = None
|
||||
if deep:
|
||||
from tools.skills_ast_audit import ast_scan_skill, format_ast_report
|
||||
|
||||
for entry in targets:
|
||||
skill_path = SKILLS_DIR / entry["install_path"]
|
||||
if not skill_path.exists():
|
||||
|
|
@ -936,6 +955,11 @@ def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> N
|
|||
|
||||
result = scan_skill(skill_path, source=entry.get("identifier", entry["source"]))
|
||||
c.print(format_scan_report(result))
|
||||
|
||||
if deep:
|
||||
ast_findings = ast_scan_skill(skill_path)
|
||||
c.print(format_ast_report(ast_findings, skill_name=entry["name"]))
|
||||
|
||||
c.print()
|
||||
|
||||
|
||||
|
|
@ -1332,7 +1356,7 @@ def skills_command(args) -> None:
|
|||
skip_confirm=getattr(args, "yes", False),
|
||||
name_override=getattr(args, "name", "") or "")
|
||||
elif action == "inspect":
|
||||
do_inspect(args.identifier)
|
||||
do_inspect(args.identifier, ast_deep=getattr(args, "ast_deep", False))
|
||||
elif action == "list":
|
||||
do_list(
|
||||
source_filter=args.source,
|
||||
|
|
@ -1343,7 +1367,8 @@ def skills_command(args) -> None:
|
|||
elif action == "update":
|
||||
do_update(name=getattr(args, "name", None))
|
||||
elif action == "audit":
|
||||
do_audit(name=getattr(args, "name", None))
|
||||
do_audit(name=getattr(args, "name", None),
|
||||
deep=getattr(args, "deep", False))
|
||||
elif action == "uninstall":
|
||||
do_uninstall(args.name)
|
||||
elif action == "reset":
|
||||
|
|
@ -1389,12 +1414,15 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
|
|||
/skills install openai/skills/skill-creator --force
|
||||
/skills install https://example.com/path/SKILL.md
|
||||
/skills inspect openai/skills/skill-creator
|
||||
/skills inspect openai/skills/skill-creator --ast-deep
|
||||
/skills list
|
||||
/skills list --source hub
|
||||
/skills check
|
||||
/skills update
|
||||
/skills audit
|
||||
/skills audit my-skill
|
||||
/skills audit --deep
|
||||
/skills audit my-skill --deep
|
||||
/skills uninstall my-skill
|
||||
/skills tap list
|
||||
/skills tap add owner/repo
|
||||
|
|
@ -1486,10 +1514,11 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
|
|||
name_override=name_override, console=c)
|
||||
|
||||
elif action == "inspect":
|
||||
if not args:
|
||||
c.print("[bold red]Usage:[/] /skills inspect <identifier>\n")
|
||||
non_flag_args = [arg for arg in args if not arg.startswith("--")]
|
||||
if not non_flag_args:
|
||||
c.print("[bold red]Usage:[/] /skills inspect <identifier> [--ast-deep]\n")
|
||||
return
|
||||
do_inspect(args[0], console=c)
|
||||
do_inspect(non_flag_args[0], console=c, ast_deep="--ast-deep" in args)
|
||||
|
||||
elif action == "list":
|
||||
source_filter = "all"
|
||||
|
|
@ -1509,8 +1538,9 @@ def handle_skills_slash(cmd: str, console: Optional[Console] = None) -> None:
|
|||
do_update(name=name, console=c)
|
||||
|
||||
elif action == "audit":
|
||||
name = args[0] if args else None
|
||||
do_audit(name=name, console=c)
|
||||
name = args[0] if args and not args[0].startswith("--") else None
|
||||
deep = "--deep" in args
|
||||
do_audit(name=name, console=c, deep=deep)
|
||||
|
||||
elif action == "uninstall":
|
||||
if not args:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue