fix(skillpack): address review — symlink read, git arg-injection, dup-subscribe, nil panics
executus CI / test (pull_request) Successful in 3m30s
executus CI / test (pull_request) Successful in 3m30s
Real issues from the PR review: - security: readTree now skips symlinks (a pack with SKILL.md -> /etc/passwd or scripts/x -> ../../.ssh/id_rsa could read host files); covers file and dir symlinks, incl. within a git subpath - security: GitSource rejects url/ref beginning with '-' (git arg injection) and clones with '--' separator; --filter=blob:none (blobless partial clone) instead of full-history clone - correctness: Subscribe no longer swallows a non-ErrNotFound store error from GetByName (would create a duplicate subscription); handles *GitSource as well as GitSource in the URL/subpath extraction - correctness: pinTo no longer renames a subscription, so Apply can't silently collide two subscriptions when an upstream pack changes its name - validation: isKebab rejects leading/trailing/consecutive hyphens; BOM- prefixed SKILL.md now parses (matches the doc comment) - robustness: Catalog/Activate/renderPackBody/Stage guard nil/malformed packs - test cleanup: Syncer.Store field renamed Cache (collided with the Store interface); test NewID returns distinct ids - tests: symlink-skip, BOM, strict-kebab, nil-pack-safety Deferred (advisory perf, documented): PackCache stores raw trees so activation re-parses; CheckAll is serial. Both fine at expected scale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-6
@@ -2,6 +2,7 @@ package skillpack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -40,10 +41,15 @@ func Resolve(ctx context.Context, cache PackCache, subs []Subscription) ([]*Pack
|
||||
// pack (name + description) plus how to load one. This is the whole prompt cost
|
||||
// of a subscription — the bodies stay out until skill_use is called.
|
||||
func Catalog(packs []*Pack) string {
|
||||
if len(packs) == 0 {
|
||||
sorted := make([]*Pack, 0, len(packs))
|
||||
for _, p := range packs {
|
||||
if p != nil && p.Manifest != nil {
|
||||
sorted = append(sorted, p)
|
||||
}
|
||||
}
|
||||
if len(sorted) == 0 {
|
||||
return ""
|
||||
}
|
||||
sorted := append([]*Pack(nil), packs...)
|
||||
sort.Slice(sorted, func(i, j int) bool { return sorted[i].Manifest.Name < sorted[j].Manifest.Name })
|
||||
|
||||
var b strings.Builder
|
||||
@@ -71,12 +77,14 @@ type skillUseArgs struct {
|
||||
// where to read scripts/references with its file tools. Leave it empty when the
|
||||
// host has no staging.
|
||||
func Activate(packs []*Pack, stagedDir string) mdagent.Skill {
|
||||
if len(packs) == 0 {
|
||||
return nil
|
||||
}
|
||||
byName := make(map[string]*Pack, len(packs))
|
||||
for _, p := range packs {
|
||||
byName[p.Manifest.Name] = p
|
||||
if p != nil && p.Manifest != nil {
|
||||
byName[p.Manifest.Name] = p
|
||||
}
|
||||
}
|
||||
if len(byName) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tool := llm.DefineTool("skill_use",
|
||||
@@ -99,6 +107,9 @@ func Activate(packs []*Pack, stagedDir string) mdagent.Skill {
|
||||
// renderPackBody is what skill_use returns: the pack's instructions plus a
|
||||
// pointer to its bundled files (with the staged path when known).
|
||||
func renderPackBody(p *Pack, stagedDir string) string {
|
||||
if p == nil || p.Manifest == nil {
|
||||
return "Error: invalid skill pack."
|
||||
}
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "# Skill: %s\n\n%s\n", p.Manifest.Name, p.Manifest.Body)
|
||||
if len(p.Bundled) > 0 {
|
||||
@@ -118,6 +129,9 @@ func renderPackBody(p *Pack, stagedDir string) string {
|
||||
// mount them (read-only is the host's concern) into a sandbox the agent's file
|
||||
// tools can read. Returns the pack's staged directory.
|
||||
func Stage(p *Pack, baseDir string) (string, error) {
|
||||
if p == nil || p.Manifest == nil {
|
||||
return "", errors.New("skillpack: Stage requires a non-nil pack")
|
||||
}
|
||||
dir := baseDir + "/" + p.Manifest.Name
|
||||
if err := p.Tree.WriteTo(dir); err != nil {
|
||||
return "", err
|
||||
|
||||
Reference in New Issue
Block a user