package tool import "fmt" // CheckAuthoring verifies that the saving user is permitted to author a // skill that uses the given tool list. Called from the save path // (skills.System.SaveUserSkill); the builtin loader bypasses this check. // // Why: the AuthoringRequirement gate is the primary admin trust boundary // for tools that can read sensitive data (db_select, repo_*) or perform // privileged Discord queries. Failing closed at save time prevents the // situation where a skill is saved-then-rejected at execute time. // // What: returns nil if all tools clear; otherwise returns the spec's // exact rejection message for the first offending tool. // // Test: see checks_test.go. func CheckAuthoring(reg Registry, tools []string, isAdmin bool) error { for _, name := range tools { t, ok := reg.Get(name) if !ok { return fmt.Errorf("unknown tool %q", name) } if t.Permission().AuthoringRequirement == RequirementAdmin && !isAdmin { return fmt.Errorf("The tool `%s` requires admin authoring. Ask an admin to create or publish a skill that uses this tool.", name) } } return nil } // CheckShareSafety verifies that none of the listed tools is unsafe for // sharing. Called when a skill's visibility is being set to shared or // public. // // Why: tools that operate on caller-private data (mortbux_get_balance, // chatbot_get_memories) leak when invoked by non-owners through a // shared/public skill — the executor would compute "the caller is // whoever ran the skill", whose data would then surface to the skill // authoring user. // // What: returns nil if all tools clear; otherwise returns the spec's // exact rejection message for the first offending tool. // // Test: see checks_test.go. func CheckShareSafety(reg Registry, tools []string) error { for _, name := range tools { t, ok := reg.Get(name) if !ok { return fmt.Errorf("unknown tool %q", name) } if !t.Permission().SafeForShare { return fmt.Errorf("The tool `%s` cannot appear in a shared skill because it operates on the caller's own data.", name) } } return nil }