Announcing PotentCodables

The framework aims to solve three major pain points experienced with Swift's Codable system:

  • Provide a library of fully featured implementations of popular serialization formats
  • Allow decoding and/or encoding values of unknown structure (i.e. any encoded tree of values)
  • Support polymorphic type encoding/decoding while still allowing Swift to implement Codable
  • Reduce the complexity and amount of code required to implement and test new serialization formats

With regard to the first point JSON, CBOR & ASN.1 are currently supported and support for YAML is coming in the near term.


You forgot to include a link! :upside_down_face:

:joy: Of course

Be mindful that using dynamic type lookup to automatically instantiate types without some sort of registration mechanism is a common security vulnerability. I would recommend against using any kind of dynamic type lookup and provide a way for code to register the set of types they expect to be able to decode at a certain point instead.


Good point, I hadn't really thought of that.

Interestingly, due to its existing limitations, I am working on a more expressive method that involves registering classes for the Ref and EmbeddedRef types. Once this is completed I'll remove the current method.

The Ref part of the readme is both difficult to understand and seems to contain incorrect code.

Very nice to see a CBOR Coder, it's a great format :+1:

Have you done any comparative benchmarks how it stacks up against the JSON coders (throughput / allocations / size)?

The framework has been updated to 1.7.0. @Joe_Groff's recommendation has finally been implemented for polymorphic decoding.

The default type lookup now requires explicit registration of allowed types. This removes any security vulnerability and allows values types (e.g. struct & enum) to be used as well.

Also, If your code happens to have an alternate method of type lookup the default implementation can be swapped out with a custom type index.

Looks great! One concern about messaging though (and take this feedback with a grain of salt because I haven't gotten a chance to use and explore your implementation) โ€” keep in mind that there still is at least one security issue that can't be resolved with type registration like this:

  1. Framework A offers a protocol/class Foo which is generally useful to conform to or subclass
  2. Your app has some subclasses of/types conforming to Foo (called Bar and Baz), which it wants to decode
  3. Framework B is a framework which your app uses, and it also finds Framework A and Foo useful, so offers its own subtype of Foo called Quux. Within its contexts, it expects to be able to encode and decode Quux, so it registers it as an expected type with the default type index
  4. Your app goes to decode a Foo expecting one of Bar or Baz but instead gets a Quux because that's what the archive contained. Depending on how you use the Foo this could end up being an attack vector or a plain old DoS vector

I think this approach is great for usability, but I would be a little wary of making certain security claims. (In practice, the risk of this is small, but still present, which is why Codable didn't offer this on its own.)

This is actually precluded by the default implementation only allowing the setting of the complete list of classes, (i.e. each call to mapAllowedTypes overwrites the previous). This makes it convenient and safe for simple applications.

Frameworks shouldn't be using Ref/EmbeddedRef as the list of allowed types is unknown; instead the should defined their own CustomRef with a custom type index. This is why the type index is swappable and frameworks should/must use this feature to make the allowed types stable and retain security.

I understand that isn't all that well documented in the README.

1 Like

Ah, this is pretty key. Unless I'm misunderstanding, in the worst-case scenario it does still seem possible (Framework B doesn't define its own custom type index, although it's supposed to, and the app forgets to call mapAllowedTypes so the type index still contains Quux instead of Bar and Baz even though it's supposed to), but the list of allowed types doesn't continue growing over time so the scenario in which this could happen is very constrained. My mistake!

This seems like a very effective approach given the circumstances; I like it!

Thanks! It's clear we need more and better documentation on the use of polymorphic decoding; specifically in how frameworks are expected to use it.

1 Like