{"record":{"id":"c68747f79b806d86","repo":"phacility/phabricator","slug":"invalid-action-s","errorCode":null,"errorMessage":"Invalid action '%s'!","messagePattern":"Invalid action '(.+?)'!","errorType":"validation","errorClass":"Exception","httpStatus":500,"severity":"error","filePath":"src/applications/policy/controller/PhabricatorPolicyEditController.php","lineNumber":107,"sourceCode":"    if ($request->isFormPost()) {\n      $data = $request->getStr('rules');\n      try {\n        $data = phutil_json_decode($data);\n      } catch (PhutilJSONParserException $ex) {\n        throw new PhutilProxyException(\n          pht('Failed to JSON decode rule data!'),\n          $ex);\n      }\n\n      $rule_data = array();\n      foreach ($data as $rule) {\n        $action = idx($rule, 'action');\n        switch ($action) {\n          case 'allow':\n          case 'deny':\n            break;\n          default:\n            throw new Exception(pht(\"Invalid action '%s'!\", $action));\n        }\n\n        $rule_class = idx($rule, 'rule');\n        if (empty($rules[$rule_class])) {\n          throw new Exception(pht(\"Invalid rule class '%s'!\", $rule_class));\n        }\n\n        $rule_obj = $rules[$rule_class];\n\n        $value = $rule_obj->getValueForStorage(idx($rule, 'value'));\n\n        $rule_data[] = array(\n          'action' => $action,\n          'rule' => $rule_class,\n          'value' => $value,\n        );\n      }\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/policy/controller/PhabricatorPolicyEditController.php#L89-L125","documentation":"Every entry in the custom-policy `rules` JSON must carry an `action` that is exactly `allow` or `deny`. The controller switches on idx($rule, 'action'); a missing action (null) or any other value falls into the default branch and throws before the rule is stored. The check runs while folding the posted data into rule_data.","triggerScenarios":"Posting a rule object without an `action` key, or with a value like \"Allow\", \"grant\", true, or null, to the policy edit endpoint.","commonSituations":"Scripts that assemble rule payloads by hand, client code ported from a different policy dialect, and replayed requests edited in an intercepting proxy.","solutions":["Give every rule object \"action\": \"allow\" or \"action\": \"deny\" (lowercase, exact).","Remove rules that should neither allow nor deny instead of inventing another action value.","Run the payload through the type guard below before POSTing."],"exampleFix":"// before\n[{\"rule\":\"PhabricatorUsersPolicyRule\",\"value\":[\"PHID-USER-xxx\"]}]\n// after\n[{\"action\":\"allow\",\"rule\":\"PhabricatorUsersPolicyRule\",\"value\":[\"PHID-USER-xxx\"]}]","handlingStrategy":"validation","validationCode":"$valid_actions = array('allow' => true, 'deny' => true);\nforeach ($payload as $rule) {\n  if (empty($valid_actions[idx($rule, 'action')])) {\n    throw new Exception(pht('Refusing to POST: rule action must be allow or deny.'));\n  }\n}","typeGuard":"function is_valid_policy_action($action) {\n  return in_array($action, array('allow', 'deny'), true);\n}","tryCatchPattern":null,"preventionTips":["Model `action` as a closed enum in client code; never pass free-text user input into it.","Validate the whole rules payload in one pass before POSTing (action, rule class, value)."],"tags":["phabricator","policy","enum-validation","custom-rules"],"backgroundTag":"enum-validation-failed","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}