{"record":{"id":"b09ef7775a9cc793","repo":"Hmbown/CodeWhale","slug":"api-key-contains-invalid-control-characters","errorCode":null,"errorMessage":"API key contains invalid control characters","messagePattern":"API key contains invalid control characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":1138,"sourceCode":"\nfn validate_device_code(code: &str) -> Result<()> {\n    if code.len() != 43\n        || !code\n            .bytes()\n            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))\n    {\n        bail!(\"The Codewhale service returned an invalid device authorization response\");\n    }\n    Ok(())\n}\n\nfn validate_api_key(key: &str) -> Result<()> {\n    let bytes = key.len();\n    if bytes < MIN_API_KEY_BYTES || bytes as u64 > MAX_API_KEY_BYTES {\n        bail!(\"API key must be {MIN_API_KEY_BYTES}-{MAX_API_KEY_BYTES} UTF-8 bytes\");\n    }\n    if key.chars().any(is_ascii_control) {\n        bail!(\"API key contains invalid control characters\");\n    }\n    Ok(())\n}\n\nfn validate_label(label: &str) -> Result<String> {\n    let label = label.split_whitespace().collect::<Vec<_>>().join(\" \");\n    if label.is_empty()\n        || label.chars().count() > MAX_KEY_LABEL_CHARS\n        || label.chars().any(is_ascii_control)\n    {\n        bail!(\"key label must contain 1-{MAX_KEY_LABEL_CHARS} characters\");\n    }\n    Ok(label)\n}\n\nfn is_ascii_control(character: char) -> bool {\n    character <= '\\u{001f}' || character == '\\u{007f}'\n}","sourceCodeStart":1120,"sourceCodeEnd":1156,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/cloud.rs#L1120-L1156","documentation":"validate_api_key rejects any key string containing ASCII control characters (<= 0x1f or 0x7f). Control characters in an API key indicate a mangled paste, embedded newline, or binary corruption, so the CLI refuses the value rather than sending a guaranteed-invalid credential to the server.","triggerScenarios":"Calling any path that runs validate_api_key (crates/cli/src/cloud.rs:1138) with a key containing characters like \\n, \\r, \\t, or 0x7f — commonly a key copied with a trailing newline or read from a file without trimming.","commonSituations":"Pasting a key from a terminal where a newline was captured, reading the key from a file with a trailing newline, terminal emulators inserting escape sequences during copy, or shell variable assignment with embedded newlines.","solutions":["Re-copy the key from the source UI in one clean selection and pass it again","If reading from a file, strip whitespace: use `--api-key-stdin` (it trims) or `tr -d '\\n\\r' < keyfile`","Check the value with `echo -n \"$KEY\" | od -c | head` to find hidden control bytes","Avoid multiline shell variables; use a single-line quoted assignment"],"exampleFix":"// before\nKEY=$(cat key.txt)                 # includes trailing \\n\n// after\nKEY=$(tr -d '\\n\\r' < key.txt)      # or use --api-key-stdin, which trims","handlingStrategy":"validation","validationCode":"fn has_control_chars(s: &str) -> bool {\n    s.chars().any(|c| c <= '\\u{001f}' || c == '\\u{007f}')\n}\n// strip before use: let clean: String = raw.trim().chars().filter(|c| !has_control_chars(&c.to_string())).collect();","typeGuard":"fn is_clean_key(s: &str) -> bool {\n    !s.is_empty() && !s.chars().any(|c| c.is_ascii_control())\n}","tryCatchPattern":null,"preventionTips":["Trim keys read from files: `tr -d '\\n\\r' < key.txt`","Copy keys in one selection; avoid terminal tools that wrap or escape text","Use --api-key-stdin rather than embedding keys in shell scripts with multiline strings"],"tags":["cli","validation","api-key"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}