{"record":{"id":"1c0bacbc3b9cc927","repo":"astrid-runtime/astrid","slug":"resolve-workspace-capsule-manifest-e","errorCode":null,"errorMessage":"resolve workspace capsule manifest: {e}","messagePattern":"resolve workspace capsule manifest: (.+?)","errorType":"http","errorClass":"GatewayError::Internal","httpStatus":500,"severity":"error","filePath":"crates/astrid-gateway/src/routes/env.rs","lineNumber":385,"sourceCode":"        .principal_home(principal)\n        .capsules_dir()\n        .join(capsule_id)\n        .join(\"Capsule.toml\");\n    if principal_manifest.exists() {\n        return parse_env_schema(&principal_manifest);\n    }\n\n    let Some(workspace_root) = workspace_root else {\n        return Err(GatewayError::NotFound);\n    };\n    let workspace = workspace_layout\n        .resolve(workspace_root)\n        .map_err(|e| GatewayError::Internal(anyhow::anyhow!(\"resolve selected workspace: {e}\")))?;\n    let manifest_relative = FsPath::new(\"capsules\")\n        .join(capsule_id)\n        .join(\"Capsule.toml\");\n    let workspace_manifest = workspace.resolve_file(&manifest_relative).map_err(|e| {\n        GatewayError::Internal(anyhow::anyhow!(\"resolve workspace capsule manifest: {e}\"))\n    })?;\n    if !workspace_manifest.exists() {\n        return Err(GatewayError::NotFound);\n    }\n    let schema = parse_env_schema(&workspace_manifest)?;\n    workspace.resolve_file(&manifest_relative).map_err(|e| {\n        GatewayError::Internal(anyhow::anyhow!(\n            \"workspace capsule manifest changed while it was being read: {e}\"\n        ))\n    })?;\n    Ok(schema)\n}\n\n#[cfg(test)]\nfn parse_env_schema(manifest_path: &FsPath) -> GatewayResult<HashMap<String, EnvFieldSchema>> {\n    let text = match std::fs::read_to_string(manifest_path) {\n        Ok(t) => t,\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/env.rs#L367-L403","documentation":"After resolving the workspace, the handler resolves the capsule's Capsule.toml relative to the workspace and wraps resolution failure as an Internal error. This fails when the path cannot be canonicalized or safely joined (e.g. path traversal or invalid capsule id), before the existence check ever runs.","triggerScenarios":"Requesting the env schema for a capsule whose id makes capsules/<id>/Capsule.toml unresolvable inside the workspace — invalid characters, traversal attempts, or a workspace that rejects the file resolution.","commonSituations":"Capsule id containing path separators or unsafe characters from a client request; capsule id casing mismatch; workspace layout changes that invalidate resolve_file.","solutions":["Validate/sanitize the capsule id before calling the env-schema route (restrict to safe identifier characters)","Confirm the capsule actually lives under capsules/<id>/Capsule.toml in the selected workspace","Check daemon/gateway logs for the underlying resolve_file error to see whether it was traversal-blocked or IO-related"],"exampleFix":"// before\nlet manifest_relative = FsPath::new(\"capsules\")\n    .join(capsule_id)\n    .join(\"Capsule.toml\");\n// after\nif capsule_id.contains('/') || capsule_id.contains('\\\\') || capsule_id.contains(\"..\") {\n    return Err(GatewayError::NotFound);\n}\nlet manifest_relative = FsPath::new(\"capsules\")\n    .join(capsule_id)\n    .join(\"Capsule.toml\");","handlingStrategy":"validation","validationCode":"fn is_safe_capsule_id(id: &str) -> bool {\n    !id.is_empty()\n        && !id.contains('/')\n        && !id.contains('\\\\')\n        && !id.contains(\"..\")\n        && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n}","typeGuard":"fn safe_capsule_id(id: &str) -> Option<&str> {\n    (is_safe_capsule_id(id)).then_some(id)\n}","tryCatchPattern":"match load_env_schema_from_home(state, capsule_id).await {\n    Ok(schema) => schema,\n    Err(GatewayError::Internal(e)) if e.to_string().contains(\"resolve workspace capsule manifest\") => {\n        tracing::warn!(%e, \"capsule manifest path unresolvable\");\n        GatewayError::NotFound\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Sanitize capsule ids at the route layer before any filesystem resolution","Keep Capsule.toml at the canonical capsules/<id>/Capsule.toml location","Add tests for traversal-style capsule ids"],"tags":["rust","filesystem","path-resolution","gateway","workspace"],"backgroundTag":"file-not-found","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}