puppetlabs/puppet · error · Puppet::Util::Windows::Error

Octet string must be an array of bytes

Error message

Octet string must be an array of bytes

What it means

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.

Source

Thrown at lib/puppet/util/windows/sid.rb:102

        Puppet.debug("Could not retrieve raw SID bytes from '#{name}': #{e.message}") unless e.code == ERROR_INVALID_SID_STRUCTURE
      end

      raw_sid_bytes ? Principal.lookup_account_sid(raw_sid_bytes) : Principal.lookup_account_name(name)
    rescue => e
      Puppet.debug(e.message.to_s)
      (allow_unresolved && raw_sid_bytes) ? unresolved_principal(name, raw_sid_bytes) : nil
    end
    module_function :name_to_principal
    class << self; alias name_to_sid_object name_to_principal; end

    # Converts an octet string array of bytes to a SID::Principal object,
    # e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for
    # S-1-5-18, the local 'SYSTEM' account.
    # Raises an Error for nil or non-array input.
    # This method returns a SID::Principal with the account, domain, SID, etc
    def octet_string_to_principal(bytes)
      if !bytes || !bytes.respond_to?('pack') || bytes.empty?
        raise Puppet::Util::Windows::Error, _("Octet string must be an array of bytes")
      end

      Principal.lookup_account_sid(bytes)
    end
    module_function :octet_string_to_principal
    class << self; alias octet_string_to_sid_object octet_string_to_principal; end

    # Converts a COM instance of IAdsUser or IAdsGroup to a SID::Principal object,
    # Raises an Error for nil or an object without an objectSID / Name property.
    # This method returns a SID::Principal with the account, domain, SID, etc
    # This method will return instances even when the SID is unresolvable, as
    # may be the case when domain users have been added to local groups, but
    # removed from the domain
    def ads_to_principal(ads_object)
      if !ads_object || !ads_object.respond_to?(:ole_respond_to?) ||
         !ads_object.ole_respond_to?(:objectSID) || !ads_object.ole_respond_to?(:Name)
        raise Puppet::Error, "ads_object must be an IAdsUser or IAdsGroup instance"
      end

View on GitHub (pinned to e227c27540)

Solutions

  1. Convert the value to bytes before calling: use `str.bytes` (or `Array(value)` packing) so the argument responds to pack.
  2. Use the string API instead: Puppet::Util::Windows::SID.name_to_principal or octet_string_to_sid_string for string-form handling.
  3. Guard upstream: if reading ADSI objectSID, normalize with `sid = ads.objectSID; sid = sid.bytes if sid.is_a?(String)`.
  4. Nil-check the source of the bytes (e.g. verify the ADSI object actually has a SID) before converting.

Example fix

# before
principal = Puppet::Util::Windows::SID.octet_string_to_principal(ads_user.objectSID)
# raises 'Octet string must be an array of bytes' when WIN32OLE returns a String

# after
raw = ads_user.objectSID
raw = raw.bytes if raw.is_a?(String)
principal = Puppet::Util::Windows::SID.octet_string_to_principal(raw)
Defensive patterns

Strategy: type-guard

Validate before calling

# normalize the SID bytes before calling
bytes = raw
bytes = bytes.bytes if bytes.is_a?(String)
bytes = bytes.unpack('C*') if bytes.is_a?(String) # alternative for packed strings
raise ArgumentError, 'SID bytes missing' if bytes.nil? || bytes.empty?

Type guard

def sid_bytes?(val)
  val.is_a?(Array) && !val.empty? && val.all? { |b| b.is_a?(Integer) && b >= 0 && b <= 255 } && val.respond_to?(:pack)
end

raise ArgumentError, 'expected octet string of bytes' unless sid_bytes?(input)

Try / catch

begin
  Puppet::Util::Windows::SID.octet_string_to_principal(bytes)
rescue Puppet::Util::Windows::Error => e
  raise unless e.message.include?('Octet string must be an array of bytes')
  raise ArgumentError, "objectSID came back as #{bytes.class}; call .bytes on it"
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/49fc29711e31b707. Report an issue: GitHub.