{"record":{"id":"af34238c0bf38032","repo":"toeverything/AFFiNE","slug":"permissiondenied","errorCode":"PermissionDenied","errorMessage":"mobile file token points outside the workspace cache directory","messagePattern":"mobile file token points outside the workspace cache directory","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"packages/frontend/mobile-native/src/cache/mod.rs","lineNumber":303,"sourceCode":"    let path = value\n      .strip_prefix(MOBILE_BLOB_FILE_PREFIX)\n      .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, \"invalid mobile file token\"))?;\n\n    let path = path.strip_prefix(\"file://\").unwrap_or(path);\n    let canonical = std::fs::canonicalize(path)?;\n    let workspace_dir = {\n      self\n        .workspace_dirs\n        .read()\n        .expect(\"workspace cache lock poisoned\")\n        .get(universal_id)\n        .cloned()\n    }\n    .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, \"workspace cache directory not registered\"))?;\n    let workspace_dir = std::fs::canonicalize(workspace_dir)?;\n\n    if !is_valid_mobile_cache_path(&canonical, &workspace_dir) {\n      return Err(std::io::Error::new(\n        std::io::ErrorKind::PermissionDenied,\n        \"mobile file token points outside the workspace cache directory\",\n      ));\n    }\n\n    let metadata = std::fs::metadata(&canonical)?;\n    if !metadata.is_file() {\n      return Err(std::io::Error::new(\n        std::io::ErrorKind::InvalidInput,\n        \"mobile file token does not resolve to a file\",\n      ));\n    }\n    if metadata.len() > MOBILE_BLOB_MAX_READ_BYTES {\n      return Err(std::io::Error::new(\n        std::io::ErrorKind::InvalidData,\n        format!(\n          \"mobile file token exceeds max size: {} > {}\",\n          metadata.len(),","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/frontend/mobile-native/src/cache/mod.rs#L285-L321","documentation":"Path-containment guard: after canonicalizing the token's path, is_valid_mobile_cache_path shows it is not inside the canonicalized workspace cache directory, indicating a traversal or tampered token; PermissionDenied is returned.","triggerScenarios":"Raised when the canonicalized token path fails the is_valid_mobile_cache_path check against the workspace cache directory, blocking path traversal.","commonSituations":"The token path resolves outside the workspace cache (symlink or ../ traversal). Treat as a corrupted or malicious blob reference and refresh the document data.","solutions":["Regenerate the token so it points inside the workspace cache dir.","Reject tokens with path traversal segments."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}