{"record":{"id":"462d9a1ca497c76a","repo":"zeroclaw-labs/zeroclaw","slug":"security-otp-gated-actions-i-contains-invalid-c","errorCode":null,"errorMessage":"security.otp.gated_actions[{i}] contains invalid characters: {normalized}","messagePattern":"security\\.otp\\.gated_actions\\[(.+?)\\] contains invalid characters: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21420,"sourceCode":"                InvalidNumericRange,\n                \"security.otp.challenge_max_attempts\",\n                \"security.otp.challenge_max_attempts must be greater than 0\"\n            );\n        }\n        for (i, action) in self.security.otp.gated_actions.iter().enumerate() {\n            let normalized = action.trim();\n            if normalized.is_empty() {\n                validation_bail!(\n                    RequiredFieldEmpty,\n                    format!(\"security.otp.gated_actions[{i}]\"),\n                    \"security.otp.gated_actions[{i}] must not be empty\"\n                );\n            }\n            if !normalized\n                .chars()\n                .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')\n            {\n                anyhow::bail!(\n                    \"security.otp.gated_actions[{i}] contains invalid characters: {normalized}\"\n                );\n            }\n            if !default_otp_gated_actions()\n                .iter()\n                .any(|known| known == normalized)\n            {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Unknown)\n                        .with_attrs(::serde_json::json!({\n                            \"action\": normalized,\n                            \"known_actions\": default_otp_gated_actions(),\n                        })),\n                    \"security.otp.gated_actions entry does not match a known gated action and will not be enforced: \"\n                );\n            }","sourceCodeStart":21402,"sourceCodeEnd":21438,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21402-L21438","documentation":"Each entry in security.otp.gated_actions is normalized first (the message prints the normalized form), then must contain only ASCII alphanumerics, '_', or '-'. This character check runs before the membership test against default_otp_gated_actions(), so a malformed name fails here and a well-formed but unknown name fails the subsequent known-actions check. Dotted, coloned, or spaced action names are the usual offenders.","triggerScenarios":"Set gated_actions to an entry like \"memory.wipe\", \"shell exec\", \"ops:restart\", or any string containing '.', ':', ' ', '@', '/' after normalization. The bail fires with the entry's index and normalized value.","commonSituations":"Inventing new gated actions without registering them in the default set; copying action IDs from a dashboard that renders them as `namespace.action`; merging config from another tool whose action IDs contain dots.","solutions":["Rewrite the entry using only a-z, A-Z, 0-9, '_', '-': \"memory_wipe\" instead of \"memory.wipe\"","After fixing the charset, confirm the name is one of default_otp_gated_actions() — unknown but well-formed names fail the very next check","Keep custom action names snake_case or kebab-case to match the existing convention"],"exampleFix":"# before\n[security.otp]\ngated_actions = [\"memory.wipe\", \"shell_exec\"]\n\n# after\n[security.otp]\ngated_actions = [\"memory_wipe\", \"shell_exec\"]","handlingStrategy":"validation","validationCode":"fn valid_gated_action(action: &str) -> bool {\n    action.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')\n}\n\nfn gated_actions_precheck(actions: &[String]) -> Result<(), String> {\n    if let Some(bad) = actions.iter().find(|a| !valid_gated_action(a)) {\n        return Err(format!(\"invalid gated action charset: {bad:?}\"));\n    }\n    Ok(())\n}","typeGuard":"fn is_sluglike_action(a: &str) -> bool {\n    !a.is_empty() && valid_gated_action(a)\n}","tryCatchPattern":"if let Err(err) = config.validate() {\n    if err.to_string().contains(\"security.otp.gated_actions\") {\n        // take the index from the message and slugify that entry (dots/colons/spaces -> '_')\n    }\n}","preventionTips":["Use snake_case or kebab-case when naming gated actions","Register custom actions in default_otp_gated_actions() before referencing them","Validate candidate action names with the same charset predicate in your config tooling"],"tags":["configuration","validation","security","otp","zeroclaw","rust"],"backgroundTag":"invalid-identifier-characters","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}