farion1231/cc-switch · error · anyhow::Error
INVALID_REPO_REF
INVALID_REPO_REF
Error message
{"code":"INVALID_REPO_REF","context":{"owner":"{owner}","name":"{name}"},"suggestion":"checkRepoUrl"} What it means
Structured error (code INVALID_REPO_REF, suggestion checkRepoUrl) from validate_repo_ref: the GitHub owner or repo name failed the charset check (is_valid_github_owner / is_valid_github_repo_name — ASCII alphanumerics plus hyphen, bounded length, non-empty). These values get formatted straight into https://github.com/{owner}/{name}/archive/refs/heads/{branch}.zip, so any URL-significant character (/ . % \\ etc.) could rewrite where the request lands.
Source
Thrown at src-tauri/src/services/skill.rs:2912
}
branch.split('/').all(|segment| {
!segment.is_empty()
&& !segment.starts_with('.')
&& !segment.ends_with('.')
&& !segment.ends_with(".lock")
})
}
/// 校验一组仓库坐标,用于任何会被拼进 github.com URL 的地方。
///
/// 动机:`download_repo` 把 owner/name/branch 直接 format 进
/// `https://github.com/{owner}/{name}/archive/refs/heads/{branch}.zip`,而 URL
/// 解析会消解点段——branch 写成 `../../../releases/download/v1/evil` 时,落点变成
/// 该仓库的 **release asset**,即攻击者可上传的任意字节。归档内容一旦可控,
/// 解压路径校验就成了唯一防线,所以这一层必须堵死。
pub(crate) fn validate_repo_ref(owner: &str, name: &str, branch: &str) -> Result<()> {
if !Self::is_valid_github_owner(owner) || !Self::is_valid_github_repo_name(name) {
return Err(anyhow!(format_skill_error(
"INVALID_REPO_REF",
&[("owner", owner), ("name", name)],
Some("checkRepoUrl"),
)));
}
if !Self::is_valid_git_branch(branch) {
return Err(anyhow!(format_skill_error(
"INVALID_REPO_REF",
&[("owner", owner), ("name", name), ("branch", branch)],
Some("checkRepoUrl"),
)));
}
Ok(())
}
/// 出口断言:URL 拼好后再确认它确实指向预期的 github.com 路径。
///
/// 这是纵深防御——即便上面的字符集校验将来漏了某种变形(百分号编码、新的View on GitHub (pinned to a2e22f3302)
Solutions
- Enter the bare GitHub login (no @, no slashes) and bare repo name
- Strip protocol, host, trailing path and .git from a pasted URL before submitting
- If the input is from a deeplink, treat this error as an attack attempt and log/reject rather than retry
Example fix
// before
let repo = SkillRepo { owner: url.clone(), name, branch }; // full URL pasted as owner
// after — extract owner/name from a pasted URL first
let trimmed = url.trim_start_matches("https://github.com/").trim_end_matches(".git");
let (owner, name) = trimmed.split_once('/').ok_or("invalid repo url")?;
SkillService::validate_repo_ref(owner, name, branch)?; Defensive patterns
Strategy: validation
Validate before calling
// Rust — validate coordinates before constructing a SkillRepo
fn parse_repo_input(owner: &str, name: &str) -> Result<()> {
let ok = |s: &str| !s.is_empty() && s.len() <= 100
&& s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-');
if !ok(owner) || !ok(name) { return Err(anyhow!("invalid owner/name")); }
Ok(())
} Type guard
export function isBareGithubSlug(owner: string, name: string): boolean {
const ok = (s: string) => /^[A-Za-z0-9-]{1,100}$/.test(s);
return ok(owner) && ok(name);
} Try / catch
match SkillService::validate_repo_ref(&owner, &name, &branch) {
Err(e) if e.to_string().contains("INVALID_REPO_REF") => {
// show 'check the repository URL' hint; do not retry with the same input
}
other => other,
} Prevention
- Parse pasted URLs client-side: strip scheme, host, and .git before splitting owner/name
- Never pass full URLs, SSH remotes, or '@user' forms in the owner field
- Treat INVALID_REPO_REF from deeplink parameters as hostile input and log it
When it happens
Trigger: Adding/discovering a repo with owner "@user", "org/team", "user..name", percent-encoded fragments, unicode, or a name containing '/' — usually a malformed paste ('github.com/user/repo/' with trailing slug), or a crafted deeplink that supplies attacker-chosen coordinates.
Common situations: Users paste full URLs or SSH remotes (git@github.com:user/repo.git) into a field expecting a bare owner/name; deeplinks (deplink.html) carrying hostile repo params; typos.
Related errors
- Unsupported URL scheme
- Invalid URL
- pi.form.absoluteHttpUrlRequired
- Invalid skill directory (possible path traversal): {director
- ARCHIVE_TOO_MANY_ENTRIES
AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16).
Data as JSON: /api/errors/31f15991828154cb.
Report an issue: GitHub.