{"record":{"id":"102ab35c570a52f7","repo":"puppetlabs/puppet","slug":"numeric-modes-must-be-in-octal-not-decimal","errorCode":null,"errorMessage":"Numeric modes must be in octal, not decimal!","messagePattern":"Numeric modes must be in octal, not decimal!","errorType":"validation","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/symbolic_file_mode.rb","lineNumber":57,"sourceCode":"    # up the bits with values 4, 2, and 1. Omitted digits are assumed to be\n    # leading zeros.\"\n    case value\n    when Numeric\n      value.to_s(8)\n    when /^0?[0-7]{1,4}$/\n      value.to_i(8).to_s(8) # strip leading 0's\n    else\n      value\n    end\n  end\n\n  def symbolic_mode_to_int(modification, to_mode = 0, is_a_directory = false)\n    if modification.nil? or modification == ''\n      raise Puppet::Error, _(\"An empty mode string is illegal\")\n    elsif modification =~ /^[0-7]+$/\n      return modification.to_i(8)\n    elsif modification =~ /^\\d+$/\n      raise Puppet::Error, _(\"Numeric modes must be in octal, not decimal!\")\n    end\n\n    fail _(\"non-numeric current mode (%{mode})\") % { mode: to_mode.inspect } unless to_mode.is_a?(Numeric)\n\n    original_mode = {\n      's' => (to_mode & 0o7000) >> 9,\n      'u' => (to_mode & 0o0700) >> 6,\n      'g' => (to_mode & 0o0070) >> 3,\n      'o' => (to_mode & 0o0007) >> 0,\n      # Are there any execute bits set in the original mode?\n      'any x?' => (to_mode & 0o0111) != 0\n    }\n    final_mode = {\n      's' => original_mode['s'],\n      'u' => original_mode['u'],\n      'g' => original_mode['g'],\n      'o' => original_mode['o'],\n    }","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/symbolic_file_mode.rb#L39-L75","documentation":"Raised by symbolic_mode_to_int when the mode is an all-digit string that contains digits outside octal range (8 or 9). The check runs after the pure-octal pattern ^[0-7]+$ fails but ^\\d+$ succeeds, so Puppet knows the value is numeric-but-decimal-looking and rejects it, matching chmod semantics.","triggerScenarios":"symbolic_mode_to_int('0999') or a manifest with mode => '648' — any digit string containing 8 or 9. Related footgun: unquoted bare integers like mode => 644 are parsed as decimal by Ruby before reaching this code.","commonSituations":"Copy-pasted Windows-style or arbitrary numbers into mode attributes; confusion between '644' and '0644' (both legal) versus values containing 8/9; generating mode strings from arithmetic.","solutions":["Rewrite the mode using octal digits only, e.g. '0644' or '0755'.","Always quote mode values in manifests so they are not treated as decimal Integers.","Check the value for 8/9 digits and typos in the copied source."],"exampleFix":"# before\nfile { '/tmp/x': mode => '648' } # 8 is not an octal digit\n\n# after\nfile { '/tmp/x': mode => '0644' }","handlingStrategy":"validation","validationCode":"s = mode.to_s\nunless s.empty? || s =~ /^[0-7]+$/ || s =~ /^([ugoa]*)([-+=].*)$/\n  raise ArgumentError, \"mode #{s.inspect} must be octal digits or symbolic\"\nend\nPuppet::Util::SymbolicMode.symbolic_mode_to_int(s)","typeGuard":"def octal_or_symbolic?(m)\n  m = m.to_s\n  !m.empty? && (m =~ /^[0-7]+$/ || m =~ /^([ugoa]*)([-+=].*)$/) ? true : false\nend","tryCatchPattern":null,"preventionTips":["Always quote numeric modes and keep them octal.","Prefer the 4-digit form ('0644') to make intent explicit.","Add rspec examples for mode parsing in wrapper code."],"tags":["puppet","file-mode","octal","permissions","validation"],"backgroundTag":"invalid-file-mode","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}