{"record":{"id":"31f15991828154cb","repo":"farion1231/cc-switch","slug":"invalid-repo-ref","errorCode":"INVALID_REPO_REF","errorMessage":"{\"code\":\"INVALID_REPO_REF\",\"context\":{\"owner\":\"{owner}\",\"name\":\"{name}\"},\"suggestion\":\"checkRepoUrl\"}","messagePattern":"(.+?)\",\"name\":\"(.+?)\"\\},\"suggestion\":\"checkRepoUrl\"\\}","errorType":"error_code","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/src/services/skill.rs","lineNumber":2912,"sourceCode":"        }\n        branch.split('/').all(|segment| {\n            !segment.is_empty()\n                && !segment.starts_with('.')\n                && !segment.ends_with('.')\n                && !segment.ends_with(\".lock\")\n        })\n    }\n\n    /// 校验一组仓库坐标，用于任何会被拼进 github.com URL 的地方。\n    ///\n    /// 动机：`download_repo` 把 owner/name/branch 直接 format 进\n    /// `https://github.com/{owner}/{name}/archive/refs/heads/{branch}.zip`，而 URL\n    /// 解析会消解点段——branch 写成 `../../../releases/download/v1/evil` 时，落点变成\n    /// 该仓库的 **release asset**，即攻击者可上传的任意字节。归档内容一旦可控，\n    /// 解压路径校验就成了唯一防线，所以这一层必须堵死。\n    pub(crate) fn validate_repo_ref(owner: &str, name: &str, branch: &str) -> Result<()> {\n        if !Self::is_valid_github_owner(owner) || !Self::is_valid_github_repo_name(name) {\n            return Err(anyhow!(format_skill_error(\n                \"INVALID_REPO_REF\",\n                &[(\"owner\", owner), (\"name\", name)],\n                Some(\"checkRepoUrl\"),\n            )));\n        }\n        if !Self::is_valid_git_branch(branch) {\n            return Err(anyhow!(format_skill_error(\n                \"INVALID_REPO_REF\",\n                &[(\"owner\", owner), (\"name\", name), (\"branch\", branch)],\n                Some(\"checkRepoUrl\"),\n            )));\n        }\n        Ok(())\n    }\n\n    /// 出口断言：URL 拼好后再确认它确实指向预期的 github.com 路径。\n    ///\n    /// 这是纵深防御——即便上面的字符集校验将来漏了某种变形（百分号编码、新的","sourceCodeStart":2894,"sourceCodeEnd":2930,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src-tauri/src/services/skill.rs#L2894-L2930","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet repo = SkillRepo { owner: url.clone(), name, branch }; // full URL pasted as owner\n\n// after — extract owner/name from a pasted URL first\nlet trimmed = url.trim_start_matches(\"https://github.com/\").trim_end_matches(\".git\");\nlet (owner, name) = trimmed.split_once('/').ok_or(\"invalid repo url\")?;\nSkillService::validate_repo_ref(owner, name, branch)?;","handlingStrategy":"validation","validationCode":"// Rust — validate coordinates before constructing a SkillRepo\nfn parse_repo_input(owner: &str, name: &str) -> Result<()> {\n    let ok = |s: &str| !s.is_empty() && s.len() <= 100\n        && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-');\n    if !ok(owner) || !ok(name) { return Err(anyhow!(\"invalid owner/name\")); }\n    Ok(())\n}","typeGuard":"export function isBareGithubSlug(owner: string, name: string): boolean {\n  const ok = (s: string) => /^[A-Za-z0-9-]{1,100}$/.test(s);\n  return ok(owner) && ok(name);\n}","tryCatchPattern":"match SkillService::validate_repo_ref(&owner, &name, &branch) {\n    Err(e) if e.to_string().contains(\"INVALID_REPO_REF\") => {\n        // show 'check the repository URL' hint; do not retry with the same input\n    }\n    other => other,\n}","preventionTips":["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"],"tags":["security","validation","github","deeplink","url"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}