use crate::common::{ManifestBuilder, TestProject};
use anyhow::Result;
use std::fs;
#[tokio::test]
async fn test_skill_resource_size_limit() -> Result<()> {
let project = TestProject::new().await?;
let source = project.create_source_repo("test").await?;
let large_content = "x".repeat(200 * 1024 * 1024); source
.create_skill(
"large-skill",
&format!(
r#"---
name: Large Skill
description: A skill with oversized content
model: claude-3-opus
---
# Large Skill
This skill contains a large file.
{}
Large content here.
"#,
large_content
),
)
.await?;
source.commit_all("Add oversized skill")?;
let source_url = source.bare_file_url(project.sources_path()).await?;
let manifest_content = ManifestBuilder::new()
.add_source("test", &source_url)
.add_skill("large-skill", |d| d.source("test").path("skills/large-skill").version("HEAD"))
.with_claude_code_tool()
.build();
project.write_manifest(&manifest_content).await?;
let result = project.run_agpm(&["install"])?;
assert!(!result.success, "Expected command to fail but it succeeded");
assert!(
(result.stderr.contains("size") || result.stderr.contains("Size"))
&& (result.stderr.contains("limit")
|| result.stderr.contains("exceeds")
|| result.stderr.contains("MB")),
"Expected error about size limit exceeded, got: {}",
result.stderr
);
assert!(!project.project_path().join(".claude/skills/agpm/large-skill").exists());
Ok(())
}
#[tokio::test]
async fn test_skill_file_count_limit() -> Result<()> {
let project = TestProject::new().await?;
let source = project.create_source_repo("test").await?;
let skill_dir = source.path.join("skills").join("many-files-skill");
fs::create_dir_all(&skill_dir)?;
fs::write(
skill_dir.join("SKILL.md"),
r#"---
name: Many Files Skill
description: A skill with too many files
model: claude-3-opus
---
# Many Files Skill
This skill has too many files.
"#,
)?;
for i in 0..1100 {
fs::write(skill_dir.join(format!("file_{:04}.txt", i)), format!("Content of file {}", i))?;
}
source.commit_all("Add skill with too many files")?;
let source_url = source.bare_file_url(project.sources_path()).await?;
let manifest_content = ManifestBuilder::new()
.add_source("test", &source_url)
.add_skill("many-files-skill", |d| {
d.source("test").path("skills/many-files-skill").version("HEAD")
})
.with_claude_code_tool()
.build();
project.write_manifest(&manifest_content).await?;
let result = project.run_agpm(&["install"])?;
assert!(!result.success, "Expected command to fail but it succeeded");
assert!(
(result.stderr.contains("file") || result.stderr.contains("File"))
&& (result.stderr.contains("limit") || result.stderr.contains("exceeds")),
"Expected error about file count limit exceeded, got: {}",
result.stderr
);
assert!(!project.project_path().join(".claude/skills/agpm/many-files-skill").exists());
Ok(())
}
#[tokio::test]
async fn test_skill_frontmatter_size_limit() -> Result<()> {
let project = TestProject::new().await?;
let source = project.create_source_repo("test").await?;
let long_description = "x".repeat(65 * 1024); let skill_content = format!(
r#"---
name: Oversized Frontmatter Skill
description: {}
model: claude-3-opus
---
# Oversized Frontmatter Skill
This skill has oversized frontmatter.
"#,
long_description
);
source.create_skill("oversized-frontmatter", &skill_content).await?;
source.commit_all("Add skill with oversized frontmatter")?;
let source_url = source.bare_file_url(project.sources_path()).await?;
let manifest_content = ManifestBuilder::new()
.add_source("test", &source_url)
.add_skill("oversized-frontmatter", |d| {
d.source("test").path("skills/oversized-frontmatter").version("HEAD")
})
.with_claude_code_tool()
.build();
project.write_manifest(&manifest_content).await?;
let result = project.run_agpm(&["install"])?;
assert!(!result.success, "Expected command to fail but it succeeded");
assert!(
result.stderr.contains("frontmatter")
&& (result.stderr.contains("size")
|| result.stderr.contains("exceeds")
|| result.stderr.contains("KB")),
"Expected error about frontmatter size exceeding limit, got: {}",
result.stderr
);
assert!(!project.project_path().join(".claude/skills/agpm/oversized-frontmatter").exists());
Ok(())
}
#[cfg(unix)] #[tokio::test]
async fn test_skill_symlink_attack_rejection() -> Result<()> {
use std::os::unix::fs::symlink;
let project = TestProject::new().await?;
let source = project.create_source_repo("test").await?;
let skill_dir = source.path.join("skills").join("symlink-skill");
fs::create_dir_all(&skill_dir)?;
fs::write(
skill_dir.join("SKILL.md"),
r#"---
name: Symlink Skill
description: A skill with a symlink attack attempt
model: claude-3-opus
---
# Symlink Skill
This skill contains a malicious symlink.
"#,
)?;
let symlink_path = skill_dir.join("sensitive-data.txt");
symlink("/etc/passwd", &symlink_path)?;
source.commit_all("Add skill with symlink attack")?;
let source_url = source.bare_file_url(project.sources_path()).await?;
let manifest_content = ManifestBuilder::new()
.add_source("test", &source_url)
.add_skill("symlink-skill", |d| {
d.source("test").path("skills/symlink-skill").version("HEAD")
})
.with_claude_code_tool()
.build();
project.write_manifest(&manifest_content).await?;
let result = project.run_agpm(&["install"])?;
assert!(!result.success, "Expected command to fail but it succeeded");
assert!(
result.stderr.contains("symlink")
|| result.stderr.contains("Symlink")
|| result.stderr.contains("not allowed"),
"Expected error about symlinks not being allowed, got: {}",
result.stderr
);
assert!(!project.project_path().join(".claude/skills/agpm/symlink-skill").exists());
Ok(())
}