mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 08:04:25 -10:00
fix(skills): validate registry keys before using them as cache directories
Cause: sync_one_skill joined the registry index key straight onto the skill cache directory and later removed, renamed and wrote under it, while the installed-skill path already ran validate_skill_name_segment. Fix: run validate_skill_name_segment on the key first and report a Failed outcome for a key that is not one path-safe segment. Test: registry_sync_refuses_a_key_that_is_not_a_single_segment (`../escape`, `..`, `a/b`, `/abs`); fails with the check disabled (1 failed). - cargo test -p codewhale-tui --lib -- skills::install: test result: ok. 21 passed; 0 failed Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
68d1377f0d
commit
92a44ba540
3 files changed
+37
No files matched your search
@@ -178,6 +178,9 @@ quieter, and Fleet runs can be checked before they spend anything.
|
||||
- Computer Use: screenshot and zoom output paths must be `.png`/`.jpg`/`.jpeg`
|
||||
files inside the recordings directory, and zoom always crops the last
|
||||
captured raster instead of a caller-named source file.
|
||||
- Skill registry sync refuses an index key that is not a single path-safe
|
||||
name before it is used as a cache directory, the same check an installed
|
||||
skill name already gets.
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -178,6 +178,9 @@ quieter, and Fleet runs can be checked before they spend anything.
|
||||
- Computer Use: screenshot and zoom output paths must be `.png`/`.jpg`/`.jpeg`
|
||||
files inside the recordings directory, and zoom always crops the last
|
||||
captured raster instead of a caller-named source file.
|
||||
- Skill registry sync refuses an index key that is not a single path-safe
|
||||
name before it is used as a cache directory, the same check an installed
|
||||
skill name already gets.
|
||||
|
||||
### Removed
|
||||
|
||||
|
||||
@@ -656,6 +656,14 @@ async fn sync_one_skill(
|
||||
cache_dir: &Path,
|
||||
max_size: u64,
|
||||
) -> SkillSyncOutcome {
|
||||
// The registry key names the cache directory joined below; it gets the
|
||||
// same single-segment check as an installed skill name.
|
||||
if let Err(err) = validate_skill_name_segment(name) {
|
||||
return SkillSyncOutcome::Failed {
|
||||
name: name.to_string(),
|
||||
reason: format!("{err:#}"),
|
||||
};
|
||||
}
|
||||
// Resolve the source to a concrete URL list.
|
||||
let source = match InstallSource::parse(&entry.source) {
|
||||
Ok(s) => s,
|
||||
@@ -1668,6 +1676,29 @@ fn hex_bytes(bytes: impl AsRef<[u8]>) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn registry_sync_refuses_a_key_that_is_not_a_single_segment() {
|
||||
let root = tempfile::tempdir().expect("tempdir");
|
||||
let cache_dir = root.path().join("cache");
|
||||
std::fs::create_dir_all(&cache_dir).expect("cache dir");
|
||||
let entry = RegistryEntry {
|
||||
source: "https://example.invalid/skill.tar.gz".to_string(),
|
||||
description: None,
|
||||
keywords: Vec::new(),
|
||||
domains: Vec::new(),
|
||||
};
|
||||
for name in ["../escape", "..", "a/b", "/abs"] {
|
||||
let outcome =
|
||||
sync_one_skill(name, &entry, &NetworkPolicy::default(), &cache_dir, 1024).await;
|
||||
assert!(
|
||||
matches!(outcome, SkillSyncOutcome::Failed { ref reason, .. }
|
||||
if reason.contains("single path-safe segment")),
|
||||
"{name}: {outcome:?}"
|
||||
);
|
||||
}
|
||||
assert!(!root.path().join("escape").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_github_source() {
|
||||
let s = InstallSource::parse("github:Hmbown/test-skill").unwrap();
|
||||
|
||||
Reference in new issue
Block a user