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:
+18
-2
@@ -112,6 +112,10 @@ func (m *Manifest) Validate() error {
|
||||
// Leading blank lines/BOM are tolerated. A missing or unterminated block is an
|
||||
// error — a SKILL.md without frontmatter has no name/description to catalog.
|
||||
func splitFrontmatter(raw []byte) (front, body []byte, err error) {
|
||||
// Strip a leading UTF-8 BOM: editors on some platforms prepend one, and
|
||||
// bytes.TrimSpace (used below) does not remove it, so a BOM would otherwise
|
||||
// make the first "---" fence unrecognizable.
|
||||
raw = bytes.TrimPrefix(raw, []byte{0xEF, 0xBB, 0xBF})
|
||||
s := bufio.NewScanner(bytes.NewReader(raw))
|
||||
s.Buffer(make([]byte, 0, 64*1024), maxBodyBytes+64*1024)
|
||||
|
||||
@@ -180,13 +184,25 @@ func trimAll(in []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// isKebab reports whether s is strict lowercase kebab-case: [a-z0-9] segments
|
||||
// joined by single hyphens, with no leading, trailing, or consecutive hyphens.
|
||||
func isKebab(s string) bool {
|
||||
if s == "" || s[0] == '-' || s[len(s)-1] == '-' {
|
||||
return false
|
||||
}
|
||||
prevHyphen := false
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', r == '-':
|
||||
case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
|
||||
prevHyphen = false
|
||||
case r == '-':
|
||||
if prevHyphen {
|
||||
return false
|
||||
}
|
||||
prevHyphen = true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return s != ""
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user