Using Symbols in TypeScript
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.
Attribute Deserialization Mitigation
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,
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:
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:
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:
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
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:
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:
The snippet above presents how
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.