{"record":{"id":"49fc29711e31b707","repo":"puppetlabs/puppet","slug":"octet-string-must-be-an-array-of-bytes","errorCode":null,"errorMessage":"Octet string must be an array of bytes","messagePattern":"Octet string must be an array of bytes","errorType":"exception","errorClass":"Puppet::Util::Windows::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/windows/sid.rb","lineNumber":102,"sourceCode":"        Puppet.debug(\"Could not retrieve raw SID bytes from '#{name}': #{e.message}\") unless e.code == ERROR_INVALID_SID_STRUCTURE\n      end\n\n      raw_sid_bytes ? Principal.lookup_account_sid(raw_sid_bytes) : Principal.lookup_account_name(name)\n    rescue => e\n      Puppet.debug(e.message.to_s)\n      (allow_unresolved && raw_sid_bytes) ? unresolved_principal(name, raw_sid_bytes) : nil\n    end\n    module_function :name_to_principal\n    class << self; alias name_to_sid_object name_to_principal; end\n\n    # Converts an octet string array of bytes to a SID::Principal object,\n    # e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for\n    # S-1-5-18, the local 'SYSTEM' account.\n    # Raises an Error for nil or non-array input.\n    # This method returns a SID::Principal with the account, domain, SID, etc\n    def octet_string_to_principal(bytes)\n      if !bytes || !bytes.respond_to?('pack') || bytes.empty?\n        raise Puppet::Util::Windows::Error, _(\"Octet string must be an array of bytes\")\n      end\n\n      Principal.lookup_account_sid(bytes)\n    end\n    module_function :octet_string_to_principal\n    class << self; alias octet_string_to_sid_object octet_string_to_principal; end\n\n    # Converts a COM instance of IAdsUser or IAdsGroup to a SID::Principal object,\n    # Raises an Error for nil or an object without an objectSID / Name property.\n    # This method returns a SID::Principal with the account, domain, SID, etc\n    # This method will return instances even when the SID is unresolvable, as\n    # may be the case when domain users have been added to local groups, but\n    # removed from the domain\n    def ads_to_principal(ads_object)\n      if !ads_object || !ads_object.respond_to?(:ole_respond_to?) ||\n         !ads_object.ole_respond_to?(:objectSID) || !ads_object.ole_respond_to?(:Name)\n        raise Puppet::Error, \"ads_object must be an IAdsUser or IAdsGroup instance\"\n      end","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/windows/sid.rb#L84-L120","documentation":"Raised by Puppet::Util::Windows::SID.octet_string_to_principal when its argument is nil, does not respond to 'pack' (i.e. is not an Array-like byte container), or is empty. The method expects the raw binary SID as an array of bytes (e.g. [1,1,0,0,0,0,0,5,18,0,0,0]) and converts it to a SID::Principal via LookupAccountSid. This is an input-contract error, not a Win32 failure.","triggerScenarios":"Passing nil (e.g. an ADSI object whose objectSID property was missing/empty), passing a SID string 'S-1-5-18' instead of bytes, or passing a String — notably newer Ruby/Win32OLE returns objectSID as a String where older Rubys returned an Array of bytes, so ads_to_principal's call octet_string_to_principal(ads_object.objectSID) starts raising this after a Ruby upgrade.","commonSituations":"Ruby upgrades changing WIN32OLE VARIANT mapping for byte arrays (objectSID comes back as a binary String); code copying group member SIDs from ADSI into local groups; passing .sid (string form) where .sid_bytes was expected; handling principals whose SID is genuinely absent.","solutions":["Convert the value to bytes before calling: use `str.bytes` (or `Array(value)` packing) so the argument responds to pack.","Use the string API instead: Puppet::Util::Windows::SID.name_to_principal or octet_string_to_sid_string for string-form handling.","Guard upstream: if reading ADSI objectSID, normalize with `sid = ads.objectSID; sid = sid.bytes if sid.is_a?(String)`.","Nil-check the source of the bytes (e.g. verify the ADSI object actually has a SID) before converting."],"exampleFix":"# before\nprincipal = Puppet::Util::Windows::SID.octet_string_to_principal(ads_user.objectSID)\n# raises 'Octet string must be an array of bytes' when WIN32OLE returns a String\n\n# after\nraw = ads_user.objectSID\nraw = raw.bytes if raw.is_a?(String)\nprincipal = Puppet::Util::Windows::SID.octet_string_to_principal(raw)","handlingStrategy":"type-guard","validationCode":"# normalize the SID bytes before calling\nbytes = raw\nbytes = bytes.bytes if bytes.is_a?(String)\nbytes = bytes.unpack('C*') if bytes.is_a?(String) # alternative for packed strings\nraise ArgumentError, 'SID bytes missing' if bytes.nil? || bytes.empty?","typeGuard":"def sid_bytes?(val)\n  val.is_a?(Array) && !val.empty? && val.all? { |b| b.is_a?(Integer) && b >= 0 && b <= 255 } && val.respond_to?(:pack)\nend\n\nraise ArgumentError, 'expected octet string of bytes' unless sid_bytes?(input)","tryCatchPattern":"begin\n  Puppet::Util::Windows::SID.octet_string_to_principal(bytes)\nrescue Puppet::Util::Windows::Error => e\n  raise unless e.message.include?('Octet string must be an array of bytes')\n  raise ArgumentError, \"objectSID came back as #{bytes.class}; call .bytes on it\"\nend","preventionTips":["Always normalize WIN32OLE objectSID with `.bytes if raw.is_a?(String)` — Ruby upgrades have changed this return type historically.","Prefer the string-based APIs (name_to_principal / valid_sid?) when you hold 'S-1-...' strings.","Nil-check the ADSI source before converting — empty objectSID means the principal cannot be resolved."],"tags":["windows","sid","ruby","win32ole","validation","puppet"],"backgroundTag":"invalid-sid-octet-string","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}