{"record":{"id":"d5c19684fa07e5ce","repo":"Hmbown/CodeWhale","slug":"fleet-task-task-id-field-path-cannot-co","errorCode":null,"errorMessage":"fleet task '{task_id}' {field} path '{}' cannot contain parent traversal","messagePattern":"fleet task '(.+?)' (.+?) path '(.+?)' cannot contain parent traversal","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/worker_runtime.rs","lineNumber":363,"sourceCode":"            matches!(\n                component,\n                std::path::Component::ParentDir\n                    | std::path::Component::RootDir\n                    | std::path::Component::Prefix(_)\n            )\n        })\n    {\n        bail!(\n            \"fleet task '{task_id}' {field} path '{}' must be one repo-relative line and cannot escape the workspace\",\n            path.display()\n        );\n    }\n    let mut segments = Vec::new();\n    for segment in raw.split('/') {\n        match segment {\n            \"\" | \".\" => {}\n            \"..\" => {\n                bail!(\n                    \"fleet task '{task_id}' {field} path '{}' cannot contain parent traversal\",\n                    path.display()\n                );\n            }\n            value => segments.push(value),\n        }\n    }\n    Ok(if segments.is_empty() {\n        \".\".to_string()\n    } else {\n        segments.join(\"/\")\n    })\n}\n\nfn fleet_coordination_contracts(task_spec: &FleetTaskSpec) -> Result<Vec<String>> {\n    let Some(value) = task_spec.metadata.get(\"coordination_contracts\") else {\n        return Ok(Vec::new());\n    };","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/fleet/worker_runtime.rs#L345-L381","documentation":"After backslash-to-slash normalization and component checks, the path still contains a literal `..` segment (e.g. \"../shared\", \"src/../../etc\", or \"..\\\\escape\" which becomes \"../escape\"). This second pass exists because on Unix a path like \"..\\\\x\" is a legal single filename that the std::path component scan would not flag — the explicit split on '/' catches the obfuscated traversal. Paths must stay inside the task workspace.","triggerScenarios":"`writable_paths = [\"..\"]`, `[\"src/../lib\"]`, or backslash-encoded traversal like `[\"..\\\\shared\\\\out\"]` in a TOML/JSON spec.","commonSituations":"Trying to grant write access to a sibling directory outside the workspace; leftover relative shorthands like \"./../build\"; adversarial specs probing sandbox boundaries.","solutions":["Remove all `..` segments and state the target directly relative to the workspace root.","If the target genuinely lives outside the repo, restructure so the task works on a copy inside the workspace, or use coordination contracts instead of a writable escape.","Sanitize generated specs by canonicalizing and re-anchoring paths before emitting them."],"exampleFix":"# before\n[workspace]\nwritable_paths = [\"src/../shared-lib\"]\n\n# after\n[workspace]\nwritable_paths = [\"shared-lib\"]","handlingStrategy":"validation","validationCode":"fn has_parent_traversal(path: &str) -> bool {\n    path.replace('\\\\', \"/\").split('/').any(|seg| seg == \"..\")\n}\n\nassert!(!has_parent_traversal(&candidate));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize with the same rule the runtime uses: backslashes are separators, '..' segments are fatal.","Generate paths by joining cleaned segments, never by string-concatenating user input.","Remember Unix treats '..\\\\x' as one filename — validate on the normalized form, not the raw one."],"tags":["fleet","path-validation","sandbox","security","rust"],"backgroundTag":"path-traversal-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}