Geek Culture
Published in

Geek Culture

Using Symbols in TypeScript

Since JavaScript introduced a primitive type called symbol in ECMAScript 2015, TypeScript supports it natively as well. The privilege of coding in this static-typed language gives developers the possibility to use types to enforce certain boundaries, and thus, speeds up the very development process. Today I will go through the most popular usages of the symbol type that could enhance the reader's workflows.

The most basic constructor invocation for a symbol.

Attribute Deserialization Mitigation

Serialisation in JavaScript might easily become tricky due to complex rules which govern the primitive and wrapper type checks, especially when talking about the typeof operator. Projects that rely on serialization done properly, usually use a well-tested external library to perform this job. Unfortunately, the usage of a specialized library might not always cover all sensitive cases.

I believe that deserialization (converting an object saved in a certain medium into an in-memory representation) should always strip the unaccounted-for attributes from an input. If that does not happen, some components of a system might move a malicious result object into privileged parts of the codebase in question, which might result in serious damage, depending on exposure. Since TypeScript does not have a pure nominal type system, but a structural one, tracking down the aforementioned problem could prove effortful even for seasoned developers.

For instance, a system that allows for non-staff user signup requires two string arguments, username and password, in a request body of a certain endpoint. After deserialization, its controller passes the result into a function capable of creating users that accept an object which contains three arguments: username, password and an optional boolean called staff. If deserialization does not drop additional attributes from the request body, then a malicious agent can explicitly send the argument staff (set to true) to obtain more rights, as shown in the example below:

An example of malicious privilege escalation.

Using a symbol-keyed attribute serves as one of many solutions to the aforementioned security threat. As symbols do not implicitly work with deserialization, the previously referenced function can expect a special attribute, defined using a symbol as its key, for controlled privilege escalation. To illustrate the change, I wrote a snippet available underneath:

An example of verified privilege escalation.

As I wrote before, some serialization libraries empower their users with possibilities for the removal of undefined attributes. My favourite library, called io-ts, supports the aforementioned feature with the exact codec builder. I usually use it in combination with the readonly and type builders for creating object codecs.

Attribute Serialization Mitigation

As hinted above, serialization (or rather JSON stringifying) omits symbols. This fact allows for defining hidden attributes in objects without worrying about their eventual exposure. I prepared a simple snippet to display the aforementioned idea in action:

An example of symbol-based attributes not being serialized.

There exist multiple ways of exposing secret information, namely the already mentioned serialization (covered), logging objects to console (cannot print symbol-keyed attributes), or enumerating keys (same as before). One could use the aforesaid technique to pass metadata information around without explicit removal of such attributes during marshaling. Before applying this pattern, I advise the reader to consider its practicality in regard to the task as hand — implicitness might work in one's advantage if applied and managed correctly.

Nominal Typing Enforcement

As I stated before, TypeScript does not behave as a nominally-typed language. There exist certain exceptions (e.g. enums), but I find treating it as a structurally-typed language first as the most reasonable approach. TypeScript developers can achieve nominal typing to some degree, as shown in the example below:

An example of nominal typing in TypeScript.

The snippet above presents how User and UserDto types, even though they share the same data attributes, use different symbol keys to distinguish between one another. In order to relieve the burden of injecting the correct symbols into respective objects from developers, I created two straightforward helper functions (the reader could think of them as constructors) that build them without exposing any symbol-based logic. As TypeScript treats these two structure as distinct types, I proved that the language in question indeed supports some aspects of nominal typing.

Summary

As presented, TypeScript adopts the symbol type from JavaScript and treats it as a first-class citizen of its type system, which allows developers to leverage symbols and design patterns to build structures that solve their architectural problems. Symbol-based constructs can shield developers from either importing or exporting potentially harmful pieces of information and make it feasible for them to declare nominal types. If you, dear reader, know of another example of how to use symbols in everyday coding situations, please share it in the comment section of this article.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store