{"record":{"id":"3ea05322ae25bb07","repo":"Hmbown/CodeWhale","slug":"uses-variable-expansion-workspace-env-values","errorCode":null,"errorMessage":"{} uses variable expansion; workspace .env values must be literal to prevent ambient-secret substitution","messagePattern":"(.+?) uses variable expansion; workspace \\.env values must be literal to prevent ambient-secret substitution","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/lib.rs","lineNumber":2516,"sourceCode":"                return Err(anyhow!(\n                    \"could not inspect {}: {error}\",\n                    candidate.display()\n                ));\n            }\n        }\n        if ancestor == boundary {\n            break;\n        }\n    }\n    Ok(None)\n}\n\nfn load_workspace_dotenv_credentials_from_path(path: &Path) -> Result<WorkspaceDotenvReport> {\n    let contents = read_stable_workspace_dotenv(path)?;\n    let text = std::str::from_utf8(&contents)\n        .map_err(|_| anyhow!(\"{} is not valid UTF-8\", path.display()))?;\n    if dotenv_has_variable_expansion(text) {\n        bail!(\n            \"{} uses variable expansion; workspace .env values must be literal to prevent ambient-secret substitution\",\n            path.display()\n        );\n    }\n\n    let mut report = WorkspaceDotenvReport {\n        path: path.to_path_buf(),\n        ..WorkspaceDotenvReport::default()\n    };\n    let entries = dotenvy::from_read_iter(std::io::Cursor::new(contents))\n        .collect::<std::result::Result<Vec<_>, _>>()\n        .map_err(|_| anyhow!(\"{} could not be parsed safely\", path.display()))?;\n    for entry in entries {\n        let (key, value) = entry;\n        if !is_workspace_dotenv_credential_key(&key) {\n            report.ignored.insert(key);\n            continue;\n        }","sourceCodeStart":2498,"sourceCodeEnd":2534,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/lib.rs#L2498-L2534","documentation":"Workspace .env credentials are loaded through load_workspace_dotenv_credentials_from_path on top of read_stable_workspace_dotenv, which accepts only literal KEY=value lines. dotenv_has_variable_expansion detects ${VAR}/$VAR interpolation syntax and rejects the whole file: expansion would let ambient environment variables substitute into the session's credential values, defeating the workspace-ownership model.","triggerScenarios":"A workspace .env containing lines like `API_KEY=${HOME_SECRET}` or `KEY=$OTHER` at the moment Codewhale loads workspace dotenv credentials (exec/serve flows that read the workspace .env).","commonSituations":"Copying a docker-compose style .env that relies on interpolation; sharing one .env across tools where compose-style expansion is expected; templated .env files committed from a skeleton.","solutions":["Replace every ${VAR}/$VAR reference with a literal value in the workspace .env","If a value must come from the environment, export it in the shell before launching codewhale instead of expanding inside .env","Keep compose-style interpolated files under a different filename that codewhale does not load","Run a quick scan: grep -E '\\$\\{|\\$[A-Za-z]' .env should find nothing"],"exampleFix":"# before (.env)\nDEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\nOPENROUTER_KEY=$OR_KEY\n\n# after (.env)\nDEEPSEEK_API_KEY=sk-literal-value\nOPENROUTER_KEY=sk-or-literal-value","handlingStrategy":"validation","validationCode":"# Reject interpolated .env before codewhale loads it\nif grep -Eq '\\$\\{|\\$[A-Za-z_]' .env 2>/dev/null; then\n  echo '.env uses variable expansion; replace with literal values'; exit 2\nfi","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep workspace .env strictly literal KEY=value","Export environment-provided secrets in the shell instead of expanding them in .env","Keep compose-style interpolated env files under another name"],"tags":["dotenv","security","env","workspace"],"backgroundTag":"dotenv-expansion-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}