<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tanker</title>
    <description>The latest articles on DEV Community by Tanker (@tanker).</description>
    <link>https://dev.to/tanker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F50%2Fcf087717-62ba-44b9-a4db-01147b842943.jpg</url>
      <title>DEV Community: Tanker</title>
      <link>https://dev.to/tanker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tanker"/>
    <language>en</language>
    <item>
      <title>The quest for a better hiring process</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Wed, 22 Jul 2020 15:20:36 +0000</pubDate>
      <link>https://dev.to/tanker/the-quest-for-a-better-hiring-process-2708</link>
      <guid>https://dev.to/tanker/the-quest-for-a-better-hiring-process-2708</guid>
      <description>&lt;h1&gt;
  
  
  The quest for a better hiring process
&lt;/h1&gt;

&lt;p&gt;I’ve been a software engineer for about ten years, and here’s something I learned: hiring software engineers is hard. One of the main challenges is to get the most information about the candidates in the minimum amount of time. You want to know if they will be a good fit for your team, and you want to know it quickly, without wasting their time.&lt;/p&gt;

&lt;p&gt;In this article, I’ll go through a list of the various techniques I’ve seen, describe the one we are using at &lt;a href="https://tanker.io"&gt;Tanker&lt;/a&gt;, and why it works best.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tour of existing techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The whiteboard
&lt;/h3&gt;

&lt;p&gt;Let’s start with the infamous whiteboard. You ask the candidates to write some code from scratch on a whiteboard to solve a given problem. This can give you an idea of how they think or how they approach new challenges, but bear in mind the whiteboard can get very intimidating for some developers, so they may appear less bright than usual.&lt;/p&gt;

&lt;p&gt;All in all, the whiteboard is a peek into the reasoning abilities of the candidate. Still, it doesn’t give any information about the ability to work in a team, understand a new codebase, or their skills with a given programming language.&lt;/p&gt;

&lt;h3&gt;
  
  
  The homework
&lt;/h3&gt;

&lt;p&gt;Another technique is to ask the candidate to implement a project “from scratch” at home. This technique avoids some of the above pitfalls, and you can quickly gauge the candidate’s level of competence.&lt;/p&gt;

&lt;p&gt;Note that you only get to see how they work alone and when writing code from scratch. Moreover, either it means a big-time investment for the candidate or the project is too small to correctly judge their skills.&lt;/p&gt;

&lt;p&gt;None of these techniques match our needs: we are a team with a flat organization in which each member has the right to contribute to important decisions. Besides, we already have a pretty large codebase, so future recruits will mostly work on &lt;em&gt;existing&lt;/em&gt; code.&lt;/p&gt;

&lt;p&gt;For these reasons, we use a &lt;em&gt;refactoring exercise&lt;/em&gt;, a technique I learned about during &lt;a href="https://www.rubytapas.com/2018/05/24/refactoring-for-interviews-nickolas-means/"&gt;an episode of Ruby Tapas presented by Nickolas Means&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As the name implies, the candidates will have to refactor some code. They also will have to demonstrate other abilities like reasoning about new features and dealing with tests.&lt;/p&gt;

&lt;p&gt;Here’s how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Refactoring Exercise
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before meeting the candidate
&lt;/h3&gt;

&lt;p&gt;First, take a simple problem and implement it. The code does not have to be very long, but you should make sure there’s room for improvement: leave out some duplication, use functions with lots of parameters, or other anti-patterns. However, do use features you want the candidate to be familiar with.&lt;/p&gt;

&lt;p&gt;Then write some basic tests and think about a new feature to add, but &lt;em&gt;don’t implement it&lt;/em&gt;. This will be important later on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the exercise
&lt;/h3&gt;

&lt;p&gt;Remember, your goal is to gather the most valuable information about the candidates: you want to see how they work when they are at their best.&lt;/p&gt;

&lt;p&gt;For instance, it’s crucial to &lt;em&gt;tell the candidate&lt;/em&gt; about the refactoring exercise, so it’s not a surprise for them to make sure &lt;em&gt;they bring their own development machine&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;When the candidate arrives, show them the code and tell them about the problem it’s solving.&lt;/p&gt;

&lt;p&gt;At this point, the code should be in a state that makes it hard to implement the new feature without refactoring.&lt;/p&gt;

&lt;p&gt;Then announce the new requirements and wait. Make it clear that the code should be production-ready.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Do they ask for existing tests?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do they try and refactor first?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your goal is to see if they think about those “good practices” without a prompt from your end. But if they start writing the new code directly, it’s not a red flag per se.&lt;/p&gt;

&lt;p&gt;You do want to see how they refactor and use tests, though, so if they skip all of the above, just gently nudge them in the right direction with questions like “Is there something you want to do before implementing the new feature?” or remarks like: “By the way, there are some tests if you think that’s helpful.”&lt;/p&gt;

&lt;p&gt;Your goal is to keep them comfortable, so don’t make them think they have failed if they did not ask about tests or refactoring right away. It happens.&lt;/p&gt;

&lt;p&gt;The rest is straightforward: watch the candidate do the refactor, implement the solution, and add tests. Note that the whole session should last about 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What running the exercise tells you
&lt;/h2&gt;

&lt;p&gt;Let’s recap what you’ve learned during the refactoring session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You have a pretty good idea about the candidates’ skills and knowledge in your programming language&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can see how they make decisions about adding features, adding tests, and architecture of their code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You make sure that they can convert requirements into code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You know how it would be like to pair-program with them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At &lt;a href="https://tanker.io"&gt;Tanker&lt;/a&gt;, we’ve been using this technique for quite some time, and it has worked very well. It gives us a good idea of how candidates will do all day, every day — and all of that in 30 minutes — not bad!&lt;/p&gt;

&lt;p&gt;Interested in joining us? &lt;a href="https://tanker.io/jobs"&gt;Have a look at our job openings&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>interview</category>
    </item>
    <item>
      <title>Automating version number updates: what could go wrong?</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Thu, 11 Jun 2020 09:35:17 +0000</pubDate>
      <link>https://dev.to/tanker/automating-version-number-updates-what-could-go-wrong-83e</link>
      <guid>https://dev.to/tanker/automating-version-number-updates-what-could-go-wrong-83e</guid>
      <description>&lt;h1&gt;
  
  
  Automating version number updates: what could go wrong?
&lt;/h1&gt;

&lt;p&gt;Say you need to update (bump) your software. It’s currently at version 1.2, all the required changes have been merged, and it’s time to publish version 1.3. That’s really easy, right? Change the version in one file, commit, tag, and push. Done!&lt;/p&gt;

&lt;p&gt;I thought that too once, but the truth is that it’s harder than it looks — let me tell you my story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Releasing software for Softbank robotics
&lt;/h2&gt;

&lt;p&gt;Our story begins in 2008, when I was release manager at Softbank Robotics.&lt;/p&gt;

&lt;p&gt;I had two big projects to release: Choregraphe, a desktop GUI to control the NAO and Pepper robots, and qiBuild, a command-line application to ease C++ development.&lt;/p&gt;

&lt;p&gt;For qiBuild, I had three files to patch because the code version number was hard-coded in several places (&lt;code&gt;setup.py&lt;/code&gt;, &lt;code&gt;__init__.py&lt;/code&gt;, and &lt;code&gt;docs/conf.py&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;On the other hand, for Choregraphe the version number was hard-coded in the top &lt;code&gt;CMakeLists.txt&lt;/code&gt; file only, but there was quite a bit of code to “forward” the version number from the top &lt;code&gt;CMake&lt;/code&gt; file down to the &lt;code&gt;version.hpp&lt;/code&gt; and &lt;code&gt;main.cpp&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Both solutions had their pros and cons but I could not decide which one was best. Since I was pretty sure I was not the first one to have encountered this issue, I started to look around for better solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying out bumpversion
&lt;/h2&gt;

&lt;p&gt;The way bumpversion works is a nice combination of the two approaches I’ve mentioned before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, keep the hard-coded version number in as many files as required&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, add a &lt;em&gt;dedicated&lt;/em&gt; configuration file (&lt;code&gt;.bumpversion.cfg&lt;/code&gt;) containing the current version and the aforementioned list of files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, when bumping the project from version X to version Y:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Iterate over the file list&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace all occurrences of X with Y, including in the configuration file itself.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I started with the qiBuild project and added the following configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# in .bumpversion.cfg
&lt;/span&gt;&lt;span class="nn"&gt;[bumpversion]&lt;/span&gt;
&lt;span class="py"&gt;current_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1.0.1&lt;/span&gt;
&lt;span class="py"&gt;commit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
&lt;span class="py"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;

&lt;span class="nn"&gt;[bumpversion:file:setup.py]&lt;/span&gt;

&lt;span class="nn"&gt;[bumpversion:file:qiBuild/__init__.py]&lt;/span&gt;

&lt;span class="nn"&gt;[bumpversion:file:docs/conf.py]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I had to bump qiBuild from 1.0.1 to 1.0.2, I ran:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bumpversion patch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A commit was automatically made along with a matching tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; git show
&lt;span class="p"&gt;commit ec50897893ce4ecfb1debaa1df266ae4c555f45b (HEAD -&amp;gt; master, tag: v1.0.2)
Author: Dimitri Merejkowsky &amp;lt;dimitri.merejkowsky@tanker.io&amp;gt;
Date: Wed May 20 16:58:27 2020 +0200
&lt;/span&gt;
Bump version: 1.0.1 → 1.0.2
&lt;span class="gh"&gt;diff --git a/.bumpversion.cfg b/.bumpversion.cfg
&lt;/span&gt;&lt;span class="gd"&gt;-------- a/.bumpversion.cfg
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/.bumpversion.cfg
&lt;/span&gt; [bumpversion]
&lt;span class="gd"&gt;-current_version = 1.0.1
&lt;/span&gt;&lt;span class="gi"&gt;+current_version = 1.0.2
&lt;/span&gt; commit = True
 tag = True

diff --git a/qiBuild/__init__.py b/qiBuild/__init__.py
&lt;span class="gd"&gt;-------- a/qiBuild/__init__.py
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/qiBuild/__init__.py
&lt;/span&gt;&lt;span class="gd"&gt;-__version__ = “1.0.1”
&lt;/span&gt;&lt;span class="gi"&gt;+__version__ = “1.0.2”
&lt;/span&gt;
diff --git a/setup.py b/setup.py
&lt;span class="gd"&gt;-------- a/setup.py
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/setup.py
&lt;/span&gt; setup(
    name="qiBuild",
&lt;span class="gd"&gt;-   version="1.0.1",
&lt;/span&gt;&lt;span class="gi"&gt;+   version="1.0.2",
&lt;/span&gt;     )

diff --git a/docs/conf.py b/docs/conf.py
&lt;span class="gd"&gt;-------- a/docs/conf.py
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/docs/conf.py
&lt;/span&gt;&lt;span class="p"&gt;project = "qiBuild"
&lt;/span&gt;&lt;span class="gd"&gt;- version="1.0.1",
&lt;/span&gt;&lt;span class="gi"&gt;+ version="1.0.2",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now I just had to run &lt;code&gt;python setup.py sdist upload&lt;/code&gt; and the 1.0.2 release was published on pypi.org.&lt;/p&gt;

&lt;p&gt;For the NAOqi project, it worked very well too.&lt;/p&gt;

&lt;p&gt;I could delete a bunch of CMake and preprocessor code and replace it with just one line of C++ code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in naoqi/version.hpp&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;NAOQI_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.3.0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time the &lt;code&gt;.bumpversion.cfg&lt;/code&gt;file looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[bumpversion]&lt;/span&gt;
&lt;span class="py"&gt;current_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2.3.0&lt;/span&gt;
&lt;span class="py"&gt;files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;include/naoqi/version.hpp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now bumping naoQI’s version could be done with a command similar to the one used to bump qiBuild.&lt;/p&gt;

&lt;p&gt;Brilliant! So, what went wrong?&lt;/p&gt;

&lt;h2&gt;
  
  
  Wandering off-track
&lt;/h2&gt;

&lt;p&gt;The problem can be described as being “too smart by half” and has to do with the command line syntax of bumpversion.&lt;/p&gt;

&lt;p&gt;Indeed, bumpversion is smart enough to bump various “parts” of the version number, namely the &lt;em&gt;major&lt;/em&gt;, &lt;em&gt;minor&lt;/em&gt;, and &lt;em&gt;patch&lt;/em&gt; components used in the semver spec.&lt;/p&gt;

&lt;p&gt;Here are some examples, assuming that the current version is 1.2.3:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bumpversion patch : 1.2.3 -&amp;gt; 1.2.4
bumpversion minor : 1.2.3 -&amp;gt; 1.3.0
bumpversion major : 1.2.3 -&amp;gt; 2.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We were using &lt;em&gt;semver&lt;/em&gt; for qiBuild and NAOqi too at the beginning — but sometimes &lt;em&gt;semver&lt;/em&gt; is not enough.&lt;/p&gt;

&lt;p&gt;Let’s continue our story. When qiBuild got more usage and the software team grew, publishing qiBuild releases started becoming… scary.&lt;/p&gt;

&lt;p&gt;New features were added, refactorings were made and qiBuild had become an essential tool for &lt;em&gt;all&lt;/em&gt; the developers in the software team (100 of them) — I was getting nervous about what would happen if I shipped a buggy release.&lt;/p&gt;

&lt;p&gt;So, with the help of members of my team, I decided to start making release candidates. That way, a few brave colleagues could help me catch bugs before everyone upgraded to the latest stable version.&lt;/p&gt;

&lt;p&gt;Since Python developers had already come up with a version scheme that allowed for release candidates, I started using that. See &lt;a href="https://www.python.org/dev/peps/pep-0440/"&gt;PEP 440&lt;/a&gt; for details. Basically, I could add a &lt;em&gt;rcX&lt;/em&gt; suffix after the &lt;em&gt;patch&lt;/em&gt; part.&lt;/p&gt;

&lt;p&gt;And… it turned out that doing so was far from trivial. Why?&lt;/p&gt;

&lt;p&gt;Well, bumpversion assumes you are using semver and, if you don’t, you need to specify a custom regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parse = (?P&amp;lt;major&amp;gt;\d+) 
 (?P&amp;lt;minor&amp;gt;\d+) 
 (\.(?P&amp;lt;patch&amp;gt;\d+))? .
 ((?P&amp;lt;release&amp;gt;rc)(?P&amp;lt;rel_num&amp;gt;\d+))?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it does not stop there.&lt;/p&gt;

&lt;p&gt;Now you need a way to tell bumpversion how to go from 2.0.0rc1 to 2.0.0rc2 (if the release candidate had bugs) or from 2.0.0rc2 to 2.0.1 (if it did not).&lt;/p&gt;

&lt;p&gt;So you add even more configuration, but it’s still not enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;serialize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
 &lt;span class="err"&gt;{major}.{minor}&lt;/span&gt;
 &lt;span class="err"&gt;{major}.{minor}.{patch}&lt;/span&gt;
 &lt;span class="err"&gt;{major}.{minor}{release}{rel_num}&lt;/span&gt;

&lt;span class="nn"&gt;[bumpversion:part:release]&lt;/span&gt;
&lt;span class="py"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
 &lt;span class="err"&gt;a&lt;/span&gt;
 &lt;span class="err"&gt;b&lt;/span&gt;
 &lt;span class="err"&gt;rc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find all the gory details in &lt;a href="https://github.com/peritus/bumpversion/issues/128"&gt;the GitHub issue&lt;/a&gt; but, in short, it was a show stopper.&lt;/p&gt;

&lt;p&gt;I believe that bumpversion is still not used at Softbank Robotics to this day, and, as far as I know, bumping qiBuild still needs version numbers to be fixed manually in several files.&lt;/p&gt;

&lt;p&gt;But our story does not end here — that would be a rather sad ending!&lt;/p&gt;

&lt;h2&gt;
  
  
  Arriving at Tanker
&lt;/h2&gt;

&lt;p&gt;In March 2016, I handed in my resignation letter at Softbank and, three months later, I started my current job at Tanker.&lt;/p&gt;

&lt;p&gt;In short, Tanker sells a product that can be used for end-to-end encryption, as well as client-side anonymization, packaged in open source SDKs (see &lt;a href="https://tanker.io"&gt;our website&lt;/a&gt; for details).&lt;/p&gt;

&lt;p&gt;Anyway, given we were releasing software and, because of my past experience, I was also in charge of release management at Tanker.&lt;/p&gt;

&lt;p&gt;As you might expect, Tanker also had hard-coded version numbers &lt;em&gt;and&lt;/em&gt; chunks of code whose only role was “forwarding” the version number from one file to another.&lt;/p&gt;

&lt;p&gt;“This is exactly the same problem I had last time!”, I thought. So, I took a look at bumpversion again — but even after all this time the bug I opened was still not fixed.&lt;/p&gt;

&lt;p&gt;That’s when I realized there were two big problems with bumpversion which would be pretty hard to fix without rewriting a lot of code.&lt;/p&gt;

&lt;p&gt;First, it’s very hard to reliably identify “parts” of version numbers. Semantics can vary from one version scheme to the next. Even &lt;a href="https://packaging.pypa.io/en/latest/_modules/packaging/version/#parse"&gt;comparing version numbers is a hard task&lt;/a&gt;, but guessing how to bump from a release candidate to a stable one is near impossible.&lt;/p&gt;

&lt;p&gt;Secondly, there are some hidden defaults at play which make understanding what’s going on under the hood pretty hard.&lt;/p&gt;

&lt;p&gt;In other words, bumpversion was “too clever by half”.&lt;/p&gt;

&lt;p&gt;So, what to do? Well, rewrite from scratch of course! (It turned out to be a good idea after all — otherwise, I would not brag about it here :P)&lt;/p&gt;

&lt;h2&gt;
  
  
  The birth of tbump
&lt;/h2&gt;

&lt;p&gt;On December 7, 2017, I started working on a rewrite called &lt;a href="https://github.com/TankerHQ/tbump"&gt;tbump&lt;/a&gt;. My goal was to keep bumpversion’s good ideas and to fix its shortcomings.&lt;/p&gt;

&lt;p&gt;Here are the main differences between tbump and bumpversion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;There are no hard-coded defaults: you must specify how the git message and the tag name will be formatted, and you also need to specify a regular expression to define the version scheme.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instead of specifying what part of the current version you want to update, you need to pass the whole new version.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back to our example — to go from 2.1.3 to 2.1.4 you run &lt;code&gt;tbump 2.1.4&lt;/code&gt; instead of &lt;code&gt;bumpversion patch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Those differences come with a price.&lt;/p&gt;

&lt;p&gt;First, since there is no hard-coded default it’s harder to use tbump out of the box.&lt;/p&gt;

&lt;p&gt;However, this one was easy to fix : I added an &lt;code&gt;init&lt;/code&gt; command to generate a &lt;code&gt;tbump.toml&lt;/code&gt; file automatically. Instead of having to read the docs, users can read the generated file and get started quickly.&lt;/p&gt;

&lt;p&gt;Secondly, since one has to specify the new version instead of a segment one wants to bump it’s easy to make mistakes, like going from 1.0.3 to 1.0.5 instead of 1.0.4.&lt;/p&gt;

&lt;p&gt;That’s where it gets interesting.&lt;/p&gt;

&lt;p&gt;You see, I was pretty annoyed by some aspects of the bumpversion UX, especially when trying to tweak the configuration file.&lt;/p&gt;

&lt;p&gt;Just watch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bumpversion patch &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&amp;lt;nothing&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;bumpversion patch &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;span class="nv"&gt;current_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.2
&lt;span class="nv"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True
&lt;span class="nv"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True
&lt;span class="nv"&gt;new_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now look at what &lt;code&gt;tbump --dry-run&lt;/code&gt; does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tbump &lt;span class="nt"&gt;--dry-run&lt;/span&gt; 1.0.3
:: Bumping from 1.0.2 to 1.0.3
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; Would patch these files
- setup.py:3 &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1.0.2"&lt;/span&gt;,
+ setup.py:3 &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1.0.3"&lt;/span&gt;,
- foo.py:1 __version__ &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.2"&lt;/span&gt;
+ foo.py:1 __version__ &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.3"&lt;/span&gt;
- tbump.toml:2 current &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.2"&lt;/span&gt;
+ tbump.toml:2 current &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.0.3"&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; Would run these git commands
&lt;span class="nv"&gt;$ &lt;/span&gt;git add &lt;span class="nt"&gt;--update&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;--message&lt;/span&gt; Bump to 1.0.3
&lt;span class="nv"&gt;$ &lt;/span&gt;git tag &lt;span class="nt"&gt;--annotate&lt;/span&gt; &lt;span class="nt"&gt;--message&lt;/span&gt; v1.0.3 v1.0.3
&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin master
&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin v1.0.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new version is all over the place, you can’t miss it, and at the same time, you see exactly what is going on.&lt;/p&gt;

&lt;p&gt;The output is similar without the — dry-run option, except changes are actually applied and git commands are run.&lt;/p&gt;

&lt;p&gt;And then I realized that every time I was bumping something, I would be following the same pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;tbump $NEW_VERSION --dry-run&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check if everything was OK by reading the output&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If not, back to Step 1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Otherwise, run &lt;code&gt;tbump $NEW_VERSION&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This looks like an algorithm — and what do we do with algorithms? We implement them!&lt;/p&gt;

&lt;p&gt;So here’s what the entry point of tbump looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# (simplified)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Looking good (y/n)?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Canceled by user"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dry_run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That way you get a chance to catch any mistake in the new version just before it’s too late :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Early adoption
&lt;/h2&gt;

&lt;p&gt;In January 2018 tbump 1.0 was out (and I had been using tbump to bump itself since the 0.2 release).&lt;/p&gt;

&lt;p&gt;I published the package on pypi.org and I started using it both for Tanker projects and my own ones.&lt;/p&gt;

&lt;p&gt;It worked great! My fellow developers told me they liked the UX so I was pretty happy.&lt;/p&gt;

&lt;p&gt;But there was a critical flaw in tbump’s design that would come back to bite us pretty hard in the future. Let’s see how.&lt;/p&gt;

&lt;h2&gt;
  
  
  yarn workspaces
&lt;/h2&gt;

&lt;p&gt;Quite early in the development of our Javascript SDK, we knew we’d be using a mono-repo.&lt;/p&gt;

&lt;p&gt;For instance, we wanted to support both Node.JS (for fast-running tests) and the browser (which is the primary use of the SDK).&lt;/p&gt;

&lt;p&gt;So right off, we knew we would have at least three packages: &lt;code&gt;@tanker/core&lt;/code&gt;, &lt;code&gt;@tanker/client-browser&lt;/code&gt;, and &lt;code&gt;@tanker/client-node&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It did not make sense to have different version numbers for those three packages, so here’s what we ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In core/package.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanker/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;libsodium-wrappers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.5.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In client-browser/package.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanker/client-browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanker/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In client-node/package.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanker/client-node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tanker/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1.2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When bumping to a new version, we needed to patch the line that contains the “version” key in the top metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/core/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"version": "{current_version}"'&lt;/span&gt;

&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/client-node/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"version": "{current_version}"'&lt;/span&gt;

&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/client-node/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"@tanker/crypto": "{current_version}"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;search&lt;/code&gt; line: we did not want to blindly replace &lt;strong&gt;all&lt;/strong&gt; occurrences of the new version in packages.json — if a line declares a third-party dependency that happens to have the same version number as the current one, it should not get patched!&lt;/p&gt;

&lt;p&gt;Speaking of dependencies, we also needed to patch the line that specifies the version of &lt;code&gt;@tanker/core&lt;/code&gt; used by &lt;code&gt;@tanker/client-browser&lt;/code&gt; and &lt;code&gt;@tanker/client-node&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/client-browser/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"@tanker/core": "{current_version}"'&lt;/span&gt;

&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/client-node/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"@tanker/core": "{current_version}"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s a total of four blocks of configuration. Then we extracted a &lt;code&gt;@tanker/crypto&lt;/code&gt; package from &lt;code&gt;@tanker/core&lt;/code&gt;, and added two new blocks of configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="c"&gt;# Bump version of @tanker/crypto&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/crypto/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"@tanker/core": "{current_version}"'&lt;/span&gt;

&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="c"&gt;# Bump version for @tanker/core too — it depends on @tanker/crypto!&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/core/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"@tanker/crypto": "{current_version}"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unbeknownst to us, that was the start of a slippery slope: every time we added a new package in the workspace, we’d have to add two blocks of configuration for this package, &lt;em&gt;and&lt;/em&gt; one for every package that depended on it.&lt;/p&gt;

&lt;p&gt;This is a famous anti-pattern, and before you can say “I see a polynomial complexity!”, we ended up with &lt;a href="https://github.com/TankerHQ/sdk-js/blob/10ca3874b1ef62dd82482e1b69ed868a280aef43/tbump.toml"&gt;this kind of monstrosity&lt;/a&gt;: a 200 hundred lines configuration file!&lt;/p&gt;

&lt;h2&gt;
  
  
  Saved by my co-workers
&lt;/h2&gt;

&lt;p&gt;Luckily I’m working with a team whose members never hesitate to share (constructive) criticism — and who often take matters into their own hands.&lt;/p&gt;

&lt;p&gt;So, when faced with the task of trying to edit this gigantic configuration file just to add a new package, one of them decided to fix the root cause of the problem and submitted a &lt;a href="https://github.com/TankerHQ/tbump/pull/39"&gt;pull request for tbump&lt;/a&gt; named “Stars and Dots”. Here are the two main changes it implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Allow regular expressions in search strings: we could now use &lt;code&gt;@tanker/.*&lt;/code&gt; instead of specifying the whole name of the dependency (&lt;code&gt;@tanker/core&lt;/code&gt;, &lt;code&gt;@tanker/crypto&lt;/code&gt;, and so on)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allow using glob patterns to specify file names. Instead of having one block per file, we could now specify a whole bunch of files using &lt;code&gt;packages/*/packages.json&lt;/code&gt; as a &lt;em&gt;glob pattern.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clever name for a clever pull request, don’t you think?&lt;/p&gt;

&lt;p&gt;This pull request was of, course, quickly reviewed and merged by yours truly, and all the nasty blocks in the &lt;code&gt;tbump.toml&lt;/code&gt; file were replaced with just two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/**/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"version": "{current_version}"'&lt;/span&gt;

&lt;span class="nn"&gt;[[file]]&lt;/span&gt;
&lt;span class="py"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"packages/**/package.json"&lt;/span&gt;
&lt;span class="py"&gt;search&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'"@tanker/[^"]+": "{current_version}"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that it remains more or less the same to this day, even if the &lt;a href="https://github.com/tankerhq/sdk-js/"&gt;sdk-js git repository&lt;/a&gt; now contains about 30 different packages :)&lt;/p&gt;

&lt;h2&gt;
  
  
  The cherry on the cake
&lt;/h2&gt;

&lt;p&gt;Before I conclude, I’d like to mention a feature of tbump called hooks. It’s the ability to run arbitrary commands before patching the files or after the commit is made and pushed.&lt;/p&gt;

&lt;p&gt;Hooks can run before the files are patched and the commit is made, or after the new commit and new tag have been pushed and are defined in the &lt;code&gt;tbump.toml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;You can find examples of this in &lt;a href="https://github.com/TankerHQ/tbump/blob/master/tbump.toml#L26-L36"&gt;tbump’s own configuration file&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[before_commit]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Check Changelog"&lt;/span&gt;
&lt;span class="py"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"grep -q {new_version} Changelog.rst"&lt;/span&gt;

&lt;span class="nn"&gt;[[after_push]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Publish to pypi"&lt;/span&gt;
&lt;span class="py"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tools/publish.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;tbump is now &lt;em&gt;the&lt;/em&gt; deployment tool for Tanker.&lt;/p&gt;

&lt;p&gt;We often use “before commit” hooks to perform various checks (like verifying that the version to be published is mentioned in the changelog), or to make sure that generated files are up-to-date (like &lt;code&gt;yarn.lock&lt;/code&gt; for instance).&lt;/p&gt;

&lt;p&gt;We also use “after push” hooks as “executable documentation” to specify how to publish new releases (like using &lt;code&gt;poetry publish&lt;/code&gt; in case of a Python package).&lt;/p&gt;

&lt;p&gt;So, what did we learn?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, pay attention to algorithmic complexity, even in configuration files, take care in designing a good UX, even for command-line tools,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, if you can afford an extra pair of eyes to a given problem, don’t hesitate to ask.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, there’s a battle-tested rewrite of bumpversion with a pretty good UX and nice features waiting for you on pypi.org. Feel free to &lt;a href="https://github.com/TankerHQ/tbump#installation"&gt;try it out&lt;/a&gt; and tell us what you think.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

&lt;p&gt;PS: We’re hiring software engineers. If the way we work sounds interesting, have a look at the hack challenge available on &lt;a href="https://www.tanker.io/rabbit/"&gt;https://www.tanker.io/rabbit/&lt;/a&gt; and don’t hesitate to reach out!&lt;/p&gt;

</description>
      <category>automation</category>
      <category>development</category>
      <category>workflow</category>
      <category>commandline</category>
    </item>
    <item>
      <title>Fantastic Bugs and Where to Find Them</title>
      <dc:creator>Timothee Isnard</dc:creator>
      <pubDate>Mon, 20 Jan 2020 16:36:23 +0000</pubDate>
      <link>https://dev.to/tanker/fantastic-bugs-and-where-to-find-them-p1b</link>
      <guid>https://dev.to/tanker/fantastic-bugs-and-where-to-find-them-p1b</guid>
      <description>&lt;p&gt;When developing &lt;a href="https://docs.tanker.io"&gt;an SDK&lt;/a&gt; running on all major platforms with bindings in half a dozen languages, you tend to run into interesting situations! Sometimes it's the kind of situation where the better part of a week turns into an extended debugging session. This is one of those times.&lt;/p&gt;

&lt;p&gt;When we started developing our Golang SDK, we made the decision to reuse &lt;a href="https://github.com/TankerHQ/sdk-native"&gt;our native library&lt;/a&gt; instead of trying to rewrite everything. Ultimately it has proved to be the right choice for us, but it's not a very well-kept secret that Golang's interoperability story with C libraries is a little more complicated than for most languages.&lt;br&gt;
Between the challenge of setting up &lt;code&gt;cgo&lt;/code&gt; (Go's FFI system) to work with our build system and the various platform-specific issues we encountered, it's fair to say the development of our Go bindings had a bit of a bumpy start.&lt;/p&gt;

&lt;p&gt;We were hopeful that things would start to settle down quietly after the initial work to get the Go SDK up and running. And if you're reading this, you won't be surprised to learn that the universe had other ideas.&lt;/p&gt;
&lt;h2&gt;
  
  
  First look
&lt;/h2&gt;

&lt;p&gt;Monday comes around, and a Go unit test is failing. Armed with the fearless resolution of someone convinced that the hard part is already behind us, we get to work.&lt;br&gt;
Strangely enough, the test seems to fail only when our native library is built in Release mode. But, before anything else, let's see what we can learn from the test suite's output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lRyl8WW1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1016/1%2AzZeErG6v7ZdcL-ZbLErtUQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lRyl8WW1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/1016/1%2AzZeErG6v7ZdcL-ZbLErtUQ.jpeg" alt="Unit test failure with a small helping of unhelpful blather"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We get some entirely normal log messages for a test that starts &lt;a href="https://docs.tanker.io/latest/guides/start/"&gt;a Tanker session&lt;/a&gt;, but it looks like the go test runner can't quite tell us which line caused the failure. We quickly decide that this is no reason to be worried. After all, how hard could fixing a single unit test be?&lt;br&gt;
Let's take a closer look in a debugger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4ADy_cU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/839/1%2AOfMsKhe2MoWNUKKNyiGzbQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4ADy_cU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/839/1%2AOfMsKhe2MoWNUKKNyiGzbQ.jpeg" alt="Visual Studios says segmentation fault with EIP == 0"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All right, so our test is actually crashing with an "Access violation at 0x00000000", the operating system's epitaph for programs in the dangerous business of dereferencing null pointers.&lt;br&gt;
Already some of you may have noticed something unusual about this crash; instead of a regular Go or C++ function, Visual Studio claims our program is currently running code at address &lt;code&gt;0x00000000&lt;/code&gt;, as if there were a function there (there &lt;em&gt;very much&lt;/em&gt; is not).&lt;/p&gt;
&lt;h3&gt;
  
  
  A closer inspection of the registers
&lt;/h3&gt;

&lt;p&gt;For those curious about low-level details Visual Studio's "Registers" view is an even more interesting clue, although not strictly necessary; finding pieces of evidence is merely a guide that helps us make better debugging decisions. However, since this post is about to dig deep, let's take a moment to appreciate just how comprehensively &lt;em&gt;wrong&lt;/em&gt; this crash is.&lt;/p&gt;

&lt;p&gt;What this view shows is the CPU's &lt;a href="https://en.wikipedia.org/wiki/Processor_register"&gt;hardware registers&lt;/a&gt;, and one of those in particular — called &lt;code&gt;EIP&lt;/code&gt; — contains the address at which our program is currently trying to run code. As a matter of fact, we could already tell &lt;code&gt;EIP&lt;/code&gt; would be 0 because of the access violation, but what's interesting is that the contents of &lt;em&gt;most of the other&lt;/em&gt; registers are also conspicuously &lt;code&gt;null&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Now &lt;em&gt;that&lt;/em&gt; is a weird state to be in.&lt;/p&gt;

&lt;p&gt;Accidentally handling null pointers can happen in the majority of mainstream languages, and corrupting memory is a dishearteningly common pastime for C programs, but registers are different. Even relatively low-level programming languages like C and C++ won't let you directly touch them (and for a good reason, those languages want to be portable, independent of hardware details). If all those registers ended up with zeros in them, this is either a particularly unlikely coincidence or the same malfunction that caused a jump to address 0 is responsible.&lt;br&gt;
Knowing this, the hope of a simple programming mistake starts to wither away as this is no mere null pointer. Which leaves those pieces of code that do mess with registers as the prime suspects — assembly code gone rogue, or the compiler toolchain itself. Needless to say, neither of those possibilities is a particularly reassuring prospect.&lt;/p&gt;
&lt;h2&gt;
  
  
  Isolating the problem
&lt;/h2&gt;

&lt;p&gt;To fix a crash like this reliably we must start by understanding what, exactly, is going wrong. The first step towards understanding this is isolating the problem. Once we've found that exact place where the tires leave the road, we can exculpate large parts of the codebase and try to reproduce the crash in a simplified environment. We need a &lt;a href="http://sscce.org/"&gt;Short, Self Contained, Correct, Example&lt;/a&gt; (also known as MCVE in &lt;a href="https://stackoverflow.com/help/minimal-reproducible-example"&gt;some corners of the web&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Since the program crashes at the null address we don't have much surrounding context to look at, but we started with a failing Go unit test so let's go back to that in a debugger.&lt;br&gt;
Thankfully, the bug looks deterministic (it fails in the same way every time), so we can follow the code until a function call crashes, repeat from a breakpoint inside the function, and quickly narrow down where things go south.&lt;/p&gt;
&lt;h3&gt;
  
  
  On the complexity of opening a debugger
&lt;/h3&gt;

&lt;p&gt;Unfortunately for us there is &lt;a href="https://en.wikipedia.org/wiki/Chaos_theory"&gt;a subtle difference&lt;/a&gt; between deterministic and predictable! A perfectly deterministic crash can disappear unless the conditions are just right (and taking a look at what makes a crash disappear can sometimes give us a hint as to what went wrong).&lt;br&gt;
We found out that this bug requires a very specific situation: only the 32-bit version of our library, only on Windows, and only when compiled with MinGW in Release mode. And that means no debug symbols.&lt;/p&gt;

&lt;p&gt;We checked that the Debug mode wouldn't crash, and against all hopes the RelWithDebInfo mode (a mix of Debug and Release) didn't crash either. But if we drill down into the compiler settings things are actually more subtle than Debug and Release. What seems to make the bug disappear when not making Release builds is when some specific optimizations — like inlining — are turned off.&lt;/p&gt;

&lt;p&gt;Up to this point I had always thought that RelWithDebInfo meant exactly the Release mode with Debug information (the &lt;code&gt;-g&lt;/code&gt; setting of the compiler) enabled, but surprise, &lt;em&gt;it also turns off a bunch of optimizations&lt;/em&gt;, just enough to make a bug disappear!&lt;/p&gt;

&lt;p&gt;Concretely, in our case, Release passes the &lt;code&gt;-O3&lt;/code&gt; flag to MinGW and RelWithDebInfo is only &lt;code&gt;-O2&lt;/code&gt;. It turns out that if we take our Release build and manually add the &lt;code&gt;-g&lt;/code&gt; flag everything crashes just fine. Awesome.&lt;/p&gt;

&lt;p&gt;Finally, we can fire up the world renowned Visual Studio debugger and follow along, right?&lt;br&gt;
Of course not! That would be too easy, and where's the fun in that?&lt;/p&gt;

&lt;p&gt;We're using Go's FFI to call our library, and Go only really works well with libraries compiled by MinGW, not Visual Studio. But, you see, MinGW emits DWARF debug information, and Visual Studio doesn't want anything to do with that when it has its own PDB debug information format. There are a few ways to deal with this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A linux-native debugger like GDB understands DWARF and can still be used on Windows,&lt;/li&gt;
&lt;li&gt;We could get a really powerful cross-platform tool like IDA Pro that understands every format under the sun,&lt;/li&gt;
&lt;li&gt;Or we could somehow try translating the DWARF debug information into a PDB for the sake of Visual Studio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Really, it's just a matter of personal preference (and having two to ten thousand dollars lying around for an IDA license). We went with &lt;a href="https://github.com/rainers/cv2pdb"&gt;option three&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
And now we're fully ready; all that's left is the tedious process of stepping through until we stumble on the program's last few actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uAhCQDyP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/982/1%2AyxiW1C8gEdp_0caD6XygFQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uAhCQDyP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/982/1%2AyxiW1C8gEdp_0caD6XygFQ.jpeg" alt="Visual Studio stopped on a breakpoint in a catch block (link to the source code below)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The unit test we're investigating tries to perform some invalid operations on purpose, so it's not surprising that the code would end up throwing an exception at some point, and we can see in the screenshot above &lt;a href="https://github.com/TankerHQ/tconcurrent/blob/533bbeb24f3b1725dff989cdba5089b359f9cc88/include/tconcurrent/packaged_task.hpp#L116-L119"&gt;the location where we catch it&lt;/a&gt;. No crash so far, but if we continue in our debugger the next thing the code does is stash the caught exception away in memory, step out of the handler, and promptly jump to 0x00000000 through a small maze of &lt;a href="https://www.boost.org/doc/libs/1_71_0/libs/context/doc/html/context/overview.html"&gt;Boost.Context&lt;/a&gt; functions. As you might have guessed, this exception handler is special. So special in fact that we need to take a moment and talk about its purpose.&lt;/p&gt;
&lt;h3&gt;
  
  
  Taking a step back: tconcurrent
&lt;/h3&gt;

&lt;p&gt;I have a confession to make — I've been hiding a few things from you. Now is probably a good time to reveal that our entire C++ library's internals are implemented as async &lt;a href="https://stackoverflow.com/questions/553704/what-is-a-coroutine"&gt;coroutines&lt;/a&gt; that return futures and await each other.&lt;br&gt;
"But wait," you might say. "C++ won't have coroutines until &lt;a href="https://stackoverflow.com/questions/43503656/what-are-coroutines-in-c20"&gt;the 2020 revision of the standard&lt;/a&gt;, and the early support in compilers is still experimental!" All very true, dear reader.&lt;br&gt;
And, at the time of writing we're in fact still held back from a full switch to C++17 due to a few legacy platforms, so we couldn't actually use the standard C++ coroutines even if they were ready.&lt;/p&gt;

&lt;p&gt;Instead we took things into our own hands and built our own &lt;a href="https://en.wikipedia.org/wiki/Async/await"&gt;async/await&lt;/a&gt; on top of the existing and very low-level &lt;a href="https://www.boost.org/doc/libs/1_71_0/libs/context/doc/html/context/overview.html"&gt;Boost.Context&lt;/a&gt; engine — this is our &lt;a href="https://github.com/TankerHQ/tconcurrent"&gt;tconcurrent library&lt;/a&gt;. It allows us to write asynchronous code almost as easily as synchronous code by enabling us to use &lt;code&gt;stackful coroutines&lt;/code&gt;. (And importantly, tconcurrent is also compatible with the &lt;a href="https://wg21.link/N4775"&gt;C++ Coroutine TS&lt;/a&gt;, meaning that as soon as we get stable compiler support we'll be able to toggle the tconcurrent back-end from Boost.Context to C++'s compiler-backed &lt;code&gt;stackless coroutines&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;So what does all of this have to do with our crash and the exception that was caught above? The exception handler we land in right before the crash actually belongs to tconcurrent.&lt;br&gt;
Coroutines work by sharing an &lt;code&gt;event loop&lt;/code&gt; running in a thread, and the exception handler we landed in is how tconcurrent prevents coroutines in their last throes from bringing down the whole program. Instead, it catches the exception and stores it in a future as the result of the prematurely terminated coroutine, and then switches back to the &lt;code&gt;event loop&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Good artists copy, great artists reproduce
&lt;/h3&gt;

&lt;p&gt;We now have a pretty good idea where the code is crashing and, good news, it doesn't look like it has anything to do with the code in our unit test or in the Go bindings for our library! Hopefully, we should be able to make it crash with just our native C++ SDK and tconcurrent, but in order to be completely sure, it's time to reduce and reproduce the bug.&lt;/p&gt;

&lt;p&gt;There are two approaches to creating a minimal reproduction of a crash. One is to take the full crashing test case and chip away at it bit by bit until only the essence of the bug is left. The other is to start with a blank slate and try to re-create the simplified circumstances of the bug, omitting anything unnecessary.&lt;br&gt;
The first way is methodic and reliable, but often slow and mind-numbingly tedious. For a large codebase with many moving parts it's often worthwhile to give the second approach a shot before falling back to the tried and true.&lt;br&gt;
We found a way to reproduce it very quickly, and I can finally show you what that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include &amp;lt;tconcurrent/coroutine.hpp&amp;gt;
#include &amp;lt;fmt/core.h&amp;gt;
&lt;/span&gt;
&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cotask&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bad_coroutine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Yeet."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;async_resumable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad_coroutine&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This code is actually pretty simple once you get past the &lt;code&gt;cotask&lt;/code&gt; and &lt;code&gt;async_resumable&lt;/code&gt; jargon! It really is the shortest possible re-creation of our failing unit test's behavior. We have a coroutine &lt;code&gt;bad_coroutine&lt;/code&gt; that throws an exception unconditionally, and a &lt;code&gt;main&lt;/code&gt; that starts &lt;code&gt;bad_coroutine&lt;/code&gt; and then immediately blocks to wait for its result — a &lt;code&gt;std::string&lt;/code&gt; exception — that it tries to catch.&lt;/p&gt;

&lt;p&gt;However, it isn't immediately clear why we bother to throw some &lt;code&gt;fmt::format&lt;/code&gt;ted text instead of just a string literal or a number. This is where the chaotic nature of the bug shines through. Any small change to the code like throwing a different kind of object or changing the &lt;code&gt;#include&lt;/code&gt; files can affect whether or not it will successfully trigger the bug. Also not visible is the fact that we still need to compile with all optimizations enabled, on 32-bit Windows, and with the MinGW compiler for the crash to happen.&lt;/p&gt;

&lt;p&gt;We arrived at this code by trying to mimic the events observed in the debugger as straightforwardly as possible, but there is always a little guesswork involved. We were lucky to stumble on a combination of factors that happened to work in just a few tries. As a result, we were able to rule out most of our codebase as unrelated to the bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Into the Thick of Things
&lt;/h2&gt;

&lt;p&gt;Having simplified the problem as much as possible, we can now focus intensely on understanding what is going on. At the start of this debugging session, I was blissfully unaware of the internals of either Boost or tconcurrent and, in fact, a substantial part of this article was new to me just a week ago! We're about to dig deeper still into some intricate details learned from carefully reading source code, documentation, and specification documents, so a humble warning — the following is correct only to the best of my knowledge (and we'll leave a dark corner partly unexplored, namely that identifying the reason why the bug was so chaotic and sometimes &lt;em&gt;did not crash&lt;/em&gt; wasn't necessary to understanding the cause and fixing it, but I'm keen to see if exceedingly curious readers can model a more complete explanation!).&lt;/p&gt;

&lt;p&gt;The first thing we learned is that the symptom of the crash is jumping to address 0, but we haven't looked in detail at how this happens. So, let's start back from our breakpoint in the tconcurrent exception handler.&lt;/p&gt;

&lt;p&gt;We now know that this catch block &lt;a href="https://github.com/TankerHQ/tconcurrent/blob/533bbeb24f3b1725dff989cdba5089b359f9cc88/include/tconcurrent/packaged_task.hpp#L116-L119"&gt;intercepts and saves exceptions&lt;/a&gt; that bubble out of coroutines, which means the &lt;code&gt;bad_coroutine&lt;/code&gt; has already finished executing by the time we get there. After a quick look at tconcurrent's source code, we learn that the next step in the case of our code above is for tconcurrent to switch back from &lt;code&gt;bad_coroutine&lt;/code&gt; to the &lt;code&gt;event loop&lt;/code&gt;, and to wake up the &lt;code&gt;main&lt;/code&gt; function that's currently still waiting for a result so we can re-throw the exception we just caught.&lt;/p&gt;

&lt;p&gt;All the interesting business seems to be happening outside our minimized repro code above and into library internals. We talked earlier about how tconcurrent defers to Boost.Context for all the low-level platform-specific coroutine switching code, but it's time to peel off the abstractions and take a look at how coroutines really work!&lt;/p&gt;

&lt;h3&gt;
  
  
  A little bit more context
&lt;/h3&gt;

&lt;p&gt;If you're not intimately familiar with them yet (as I wasn't), the advantage a coroutine has over a regular function is that it can &lt;code&gt;suspend&lt;/code&gt; itself instead of blocking the thread, and another coroutine that's ready to run will be &lt;code&gt;resume&lt;/code&gt;d in its place (a form of &lt;a href="https://en.wikipedia.org/wiki/Cooperative_multitasking"&gt;cooperative multitasking&lt;/a&gt;). Eventually, whatever the first coroutine was waiting for will become available, making it possible to &lt;code&gt;resume&lt;/code&gt; it (at the time the second coroutine is next &lt;code&gt;suspend&lt;/code&gt;ed). All of those coroutines can share and wait for their turn to run on the same thread, and in our case this is the thread that hosts the tconcurrent &lt;code&gt;event loop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This may sound a little convoluted compared to plain old blocking threads, but the whole point of the system is the large efficiency gain: having many coroutines consumes little memory and switching between them is considerably faster than having many blocked threads and relying on the operating system to juggle them (and the gap has been growing, as &lt;a href="https://en.wikipedia.org/wiki/Category:Speculative_execution_security_vulnerabilities"&gt;the recent waves of CPU vulnerability mitigations&lt;/a&gt; continue to increase the cost of thread switching). In fact, coroutines are often called lightweight threads because of this.&lt;/p&gt;

&lt;p&gt;So, if coroutines are similar to threads, this means tconcurrent and especially Boost.Context are going to have a job that looks a lot like a simplified operating system scheduler. Just like a thread, each &lt;code&gt;stackful coroutine&lt;/code&gt; has its own stack that we need to keep track of, and its own set of CPU registers that must be saved when we &lt;code&gt;suspend&lt;/code&gt; and restored as we &lt;code&gt;resume&lt;/code&gt; it. This is exactly what Boost.Context takes care of. A &lt;code&gt;context&lt;/code&gt; object can be seen as a suspended coroutine and contains all the state necessary to resume it!&lt;/p&gt;

&lt;p&gt;So, if we go back to our minimized code above, the control flow starts to become a little clearer. Conceptually we start in &lt;code&gt;main&lt;/code&gt; and we create a new coroutine, with its own context and stack, for the &lt;code&gt;bad_coroutine&lt;/code&gt; function (that's what &lt;code&gt;tc::async_resumable&lt;/code&gt; does). Then we block &lt;code&gt;main&lt;/code&gt; while the tconcurrent &lt;code&gt;event loop&lt;/code&gt; switches to &lt;code&gt;bad_coroutine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As we switch away from the &lt;code&gt;event loop&lt;/code&gt; we &lt;code&gt;suspend&lt;/code&gt; it, and so Boost.Context gives us a corresponding context that we can use to &lt;code&gt;resume&lt;/code&gt; the &lt;code&gt;event loop&lt;/code&gt; as soon as &lt;code&gt;bad_coroutine&lt;/code&gt; finishes executing.&lt;/p&gt;

&lt;p&gt;Speaking of &lt;code&gt;bad_coroutine&lt;/code&gt;, the first thing it does is throw an exception and so we end up in the tconcurrent exception handler.&lt;br&gt;&lt;br&gt;
We save the caught exception, and now that &lt;code&gt;bad_coroutine&lt;/code&gt; has ended we try to &lt;code&gt;resume&lt;/code&gt; our &lt;code&gt;event loop&lt;/code&gt; using its saved context (and, at the same time, we can unblock our &lt;code&gt;main&lt;/code&gt; now that we have a result).&lt;/p&gt;

&lt;p&gt;And this last &lt;code&gt;resume&lt;/code&gt; is the point where we end up zeroing half our registers and jumping to address zero. If there was ever a time to suspect that memory used by some &lt;del&gt;horribly cursed&lt;/del&gt; intricately woven assembly code is getting corrupted, this is it!&lt;/p&gt;

&lt;p&gt;To complicate matters slightly, the exact content of a &lt;code&gt;context&lt;/code&gt; object (that is, the serialized state of a &lt;code&gt;suspend&lt;/code&gt;ed coroutine in memory) and the way it is saved is &lt;em&gt;extremely&lt;/em&gt; platform-specific — as evidenced by the x86 registers in it — so I'm going to talk only about what is used in our particular case of i386 Windows with the MinGW compiler. This is the cryptic table shown below and extracted from &lt;a href="https://github.com/boostorg/context/blob/bebf903239907ff44d4479ea5c41fea7649e7d6e/src/asm/jump_i386_ms_pe_gas.asm#L9-L24"&gt;a comment in Boost.Context's low-level assembly files&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  -----------------------------------------------------------------
  |   0   |   1   |   2   |   3   |   4   |   5   |   6   |   7   |
  -----------------------------------------------------------------
  |   0h  |  04h  |  08h  |  0ch  |  010h |  014h |  018h |  01ch |
  -----------------------------------------------------------------
  | mxcsr | x87_cw|fi_strg|dealloc| limit |  base | fc_seh|  EDI  |
  -----------------------------------------------------------------
  -----------------------------------------------------------------
  |   8   |   9   |  10   |   11  |   12  |   13  |   14  |   15  |
  -----------------------------------------------------------------
  |  020h | 024h  | 028h  |  02ch |  030h |  034h |  038h |  03ch |
  -----------------------------------------------------------------
  |  ESI  |  EBX  |  EBP  |  EIP  |   to  |  data | EH NXT|EH HDLR|
  -----------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What you see in each column of this table is a different piece of information that Boost.Context saves when a coroutine is suspended (or when its context is first created), but don't worry about what exactly each of those is for. What we're particularly interested in is the registers &lt;code&gt;EDI&lt;/code&gt; through &lt;code&gt;EIP&lt;/code&gt;, and if you remember the first time we looked at registers at the start of this article, you might be surprised that we're saving only a few of them in this table. This is &lt;a href="https://en.wikipedia.org/wiki/X86_calling_conventions#Register_preservation"&gt;a small trick of the ABI, &lt;code&gt;callee-saved registers&lt;/code&gt;&lt;/a&gt;, that becomes important here.&lt;/p&gt;

&lt;p&gt;Boost.Context uses &lt;a href="https://github.com/boostorg/context/blob/bebf903239907ff44d4479ea5c41fea7649e7d6e/src/asm/jump_i386_ms_pe_gas.asm#L37"&gt;a hand-written assembly routine&lt;/a&gt; to switch from one context to another, but to the compiler it looks like any other function, and like any other function on i386 Windows the compiler takes care to save some registers (on the stack) before calling the function, as mandated by the ABI. Also, like any other function, the compiler pushes the return address of the caller on the stack before doing the call, so that it knows where to return. This means that by the time we're in Boost.Context's assembly routine, a lot has already been saved onto the stack for us.&lt;/p&gt;

&lt;p&gt;You might want to take some time to internalize this, because here comes the real magic: since each coroutine has its own stack, we know that as long as the coroutine is suspended anything we save on its stack is safe and won't be overwritten. So the assembly routine that &lt;code&gt;suspend&lt;/code&gt;s the coroutine can simply save the contents of the context right here on the stack while it's &lt;code&gt;suspend&lt;/code&gt;ing, next to the return address and the &lt;code&gt;callee-saved registers&lt;/code&gt; saved by the compiler!&lt;br&gt;
And the context object that Boost.Context gives us is really just a pointer to this &lt;code&gt;suspend&lt;/code&gt;ed coroutine's stack.&lt;br&gt;
When it is time to &lt;code&gt;resume&lt;/code&gt; Boost takes this pointer, restores the context's contents manually in assembly, uses it as its new stack pointer, and then "simply" returns from the magic assembly routine back to the calling code that originally &lt;code&gt;suspend&lt;/code&gt;ed the coroutine — like any other function!&lt;/p&gt;

&lt;p&gt;Okay, this is a lot to take in, so here's the takeaway — each coroutine has its own stack that no one touches while it's &lt;code&gt;suspend&lt;/code&gt;ed, and saving the context on that stack is the last thing a &lt;code&gt;suspend&lt;/code&gt;ed coroutine does. There's no obvious bug in the Boost code, and while the rest of the tconcurrent code does keep pointers to those stack contexts, the only times they are really used is when saving and resuming coroutines. So, if we don't have any particularly suspicious code that accesses the context, what could possibly be corrupting it and how do we find out?&lt;/p&gt;
&lt;h3&gt;
  
  
  To catch a corruption
&lt;/h3&gt;

&lt;p&gt;Finally, we have a promising trail to follow, and a strong suspicion that the saved context we switch back to is being corrupted by &lt;em&gt;something&lt;/em&gt;, since it somehow ends up filled with zeros when we try to &lt;code&gt;resume&lt;/code&gt; it. The good news is that now that we have a precise target — the saved registers on the stack — there's one simple way to track whatever is causing this kerfuffle. Back to our debugger!&lt;/p&gt;

&lt;p&gt;This time we're going to make use of a particularly awesome feature of x86 CPUs — &lt;a href="https://en.wikipedia.org/wiki/X86_debug_register"&gt;hardware data breakpoints&lt;/a&gt; (these are sometimes known as &lt;code&gt;watchpoints&lt;/code&gt; and emulated in software by single-stepping, which, unfortunately runs hundreds of times slower; the other side of that coin being that the hardware is limited to only four breakpoints at the same time).&lt;/p&gt;

&lt;p&gt;The principle is as simple as it sounds. Instead of breaking on a line of code we're going to set a breakpoint on a location in memory and ask the debugger to break when the value in that location is modified.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--euuK9fnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/982/1%2AN6-ZIgG7LvLh2cSC2aOd0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--euuK9fnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://miro.medium.com/max/982/1%2AN6-ZIgG7LvLh2cSC2aOd0w.png" alt="Visual Studio stopped in _Unwind_SjLj_Resume (source link below)"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, I didn't say this was going to be an easy bug — the data breakpoint takes us deep into the internal plumbing of the compiler runtime.&lt;/p&gt;

&lt;p&gt;The actual corruption happens when some innocent-looking code saves a value on the stack — the &lt;strong&gt;completely wrong stack&lt;/strong&gt; of our suspended &lt;code&gt;event loop&lt;/code&gt;, overwritting our saved context! A buggy function or some invalid arguments causing the program to corrupt memory would have been perfectly normal and expected, but finding some internal runtime code merrily running on the wrong stack trampling all over our saved data is the special kind of fun one is really looking for in a debugging session.&lt;/p&gt;

&lt;p&gt;The screenshot above is actually a little bit closer to familiar grounds, in a parent function of where the corruption &lt;em&gt;usually&lt;/em&gt; happens (as the exact point depends on the chaotic behavior).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gcc-mirror/gcc/blob/fbd263526ad105a953fd51d9f7bca2c3f268cf82/libgcc/unwind.inc#L225-L248"&gt;This is the function&lt;/a&gt; that'll send us to the next arcane topic everyone would really be better off never having to run into: &lt;code&gt;_Unwind_SjLj_Resume&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  SjLj and other four letter words
&lt;/h3&gt;

&lt;p&gt;Have you ever wondered how exceptions are implemented by your compiler? No? Well, wonder no more. We're going to need a crash course in exception handling to understand what the code we landed into even is.&lt;/p&gt;

&lt;p&gt;First of all, &lt;code&gt;SjLj&lt;/code&gt; stands for &lt;code&gt;Setjmp/Longjmp&lt;/code&gt;, an archaic way of implementing exception handling &lt;a href="https://llvm.org/docs/ExceptionHandling.html#setjmp-longjmp-exception-handling"&gt;notorious for slowing programs in the common non-throwing path&lt;/a&gt; and based on &lt;a href="https://en.wikipedia.org/wiki/Setjmp.h"&gt;an ancient standard C header that's otherwise best left alone&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are of course &lt;a href="https://stackoverflow.com/questions/15670169/what-is-difference-between-sjlj-vs-dwarf-vs-seh"&gt;better modern ways to implement exceptions&lt;/a&gt;, namely the Dwarf2 model which is common on Linux, or SEH-based exception handling which is a whole larger system integrated with Windows.&lt;/p&gt;

&lt;p&gt;So why are we using SjLj, then? MinGW doesn't support SEH exceptions on i386 Windows, and as for Dwarf2 exceptions they come with their own explosive downsides since a thrown Dwarf2 exception will crash the program if it bubbles up into non-dwarf2 aware code (note though, this is actually OK in the case of our core library, since it currently exposes a C interface to bindings and doesn't leak exceptions). SjLj exceptions, apart from their age and ambient overhead, have a reputation for being a sane battle-tested default.&lt;/p&gt;

&lt;p&gt;And what about this &lt;code&gt;_Unwind_SjLj_Resume&lt;/code&gt; function we stopped in? This is where we run into &lt;a href="https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html"&gt;the C++ exception handling ABI for Itanium&lt;/a&gt;. (&lt;a href="https://en.wikipedia.org/wiki/Itanic"&gt;Itanium&lt;/a&gt; is the experimental architecture that failed to become the &lt;a href="https://en.wikipedia.org/wiki/X86-64"&gt;x86_64 we use today&lt;/a&gt;, but did define most parts of our modern ABI).&lt;/p&gt;

&lt;p&gt;While it makes for excellent bedside reading, I'm going to try and spare you as many of the boring details of the spec as possible. What we do need to know about this ABI is that it standardizes an interface for stack unwinding — the process of going up the stack and running exception handlers when a program throws an exception. One side of this interface is implemented by the compiler (in our case, with the SjLj method), and the other is the unwinding library described in the ABI. (And why isn't that unwinding library just an internal detail of the compiler? Because making it a separate language-independent facility enables C++ and other languages' exceptions to work together transparently, without needing every language to know about each other!)&lt;/p&gt;

&lt;p&gt;There are ten functions provided by the unwinding library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  _Unwind_RaiseException,
  _Unwind_Resume,
  _Unwind_DeleteException,
  _Unwind_GetGR,
  _Unwind_SetGR,
  _Unwind_GetIP,
  _Unwind_SetIP,
  _Unwind_GetRegionStart,
  _Unwind_GetLanguageSpecificData,
  _Unwind_ForcedUnwind
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We don't need to understand the details of what each of these do — we can leave that to compiler writers — but we can see that &lt;code&gt;_Unwind_Resume&lt;/code&gt; is defined here, which is, in fact, the same function as our &lt;code&gt;_Unwind_SjLj_Resume&lt;/code&gt;. Now, according to the ABI document, this function is called &lt;code&gt;after executing cleanup code in a partially unwound stack&lt;/code&gt; and &lt;code&gt;at the end of a landing pad that performed cleanup, but did not resume normal execution&lt;/code&gt;. That's not very concrete, so let's look at what happens with the unwinding library when a program throws an exception.&lt;/p&gt;

&lt;p&gt;A C++ &lt;code&gt;throw&lt;/code&gt; statement is replaced by a call to &lt;code&gt;__cxa_throw&lt;/code&gt; by the compiler, but the real work is delegated to the &lt;code&gt;_Unwind_RaiseException&lt;/code&gt; function, which is called to start the stack unwinding process. This process works in two phases: the &lt;em&gt;search phase&lt;/em&gt; walks up the stack through every stack frame to locate exception handlers, and if that first phase is successful the &lt;em&gt;cleanup phase&lt;/em&gt; starts walking the stack frames again, but this time calling every exception handler found by the &lt;em&gt;search phase&lt;/em&gt; on the way. (What happens if the &lt;em&gt;search phase&lt;/em&gt; fails? That's an unhandled exception and the program terminates.)&lt;/p&gt;

&lt;p&gt;So, to simplify, the &lt;em&gt;cleanup phase&lt;/em&gt; is essentially responsible for running the destructor of every object that goes out of scope on the way to the nearest catch block. As far as the exception runtime knows, both destructors and catch blocks are exception handlers (what the spec calls &lt;code&gt;landing pads&lt;/code&gt;), the main difference being that catch blocks stop the unwinding and resume normal code execution but, after a destructor is run, the frame's landing pad has to tell the runtime to continue going down the stack. And how does it do that? That's just what our &lt;code&gt;_Unwind_Resume&lt;/code&gt; function does!&lt;/p&gt;

&lt;p&gt;All right, so that doesn't quite tell us yet why &lt;code&gt;_Unwind_Resume&lt;/code&gt; is running on the wrong stack, but at least we're starting to have some idea where we landed and what is happening in this function. In fact, we're getting really close! The last piece of the puzzle is going to come from the &lt;em&gt;search phase&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Since the unwinding library is a generic language-independent algorithm, it knows how to walk up a stack but it doesn't actually know how to find any C++ catch blocks or destructors that need to be run. To do that, the compiler has to provide what's called a &lt;a href="https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#base-personality"&gt;personality routine&lt;/a&gt;, which is going to be &lt;a href="https://github.com/gcc-mirror/gcc/blob/b1e6e489b54a1bc21fba00740154c49c62eb3ea8/libstdc%2B%2B-v3/libsupc%2B%2B/eh_personality.cc#L338-L345"&gt;specific to the exception handling mechanism&lt;/a&gt; (and if you've ever had confusing link errors about &lt;code&gt;__gxx_personality_v0&lt;/code&gt;, now you'll know that weird symbol is actually a personality routine, which has to match the exception-handling mechanism!).&lt;/p&gt;

&lt;p&gt;Of course, everything I've described so far happens in the perfect abstract world of the Itanium specification, where our Sufficiently Smart Compilers provide modern, zero-cost, table-based exception handling. What makes those exception handling mechanisms so efficient is that they don't need to do anything at runtime as long as an exception isn't thrown. Instead, during the &lt;em&gt;search phase&lt;/em&gt; the personality routine can get all the information it needs by looking up some tables embedded in the binary.&lt;/p&gt;

&lt;p&gt;Back to SjLj, since it doesn't embed any tables the program has to re-create that same information at runtime, and compiler writers needed a way to shoehorn this old runtime system into the new world of fast inter-operable exceptions. They did that by introducing two new functions that the compiler would sprinkle throughout the code, to keep track of the entire context the &lt;em&gt;search phase&lt;/em&gt; needs: &lt;code&gt;_Unwind_SjLj_Register&lt;/code&gt; and &lt;code&gt;_Unwind_SjLj_Unregister&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Denouement
&lt;/h3&gt;

&lt;p&gt;And this is where everything starts to fall into place!&lt;/p&gt;

&lt;p&gt;This SjLj context that needs to be maintained at runtime? That contains our stack pointer, saved by &lt;code&gt;_Unwind_SjLj_Register&lt;/code&gt; at the start of every function that might need to run a destructor or catch an exception, and restored by the unwinder before any call to an exception handling landing-pad.&lt;/p&gt;

&lt;p&gt;But our async code uses stackful coroutines! In other words, every coroutine has its own stack, and whenever tconcurrent switches coroutines the stack pointer is quietly updated by Boost Context's internal assembly routines — something that the compiler and the unwinder can't possibly know about.&lt;/p&gt;

&lt;p&gt;So, we end up in a situation where &lt;code&gt;_Unwind_SjLj_Register&lt;/code&gt; has saved a stack pointer and since then, we've switched to a completely different stack without updating SjLj's information. After a few coroutine switches, the chain of SjLj contexts that the Register/Unregister functions maintain has turned into a complete mess. (By contrast, other exception mechanisms don't need to save stack pointers or any other information at runtime, so they don't have an equivalent of the SjLj chain — nothing that needs updating.)&lt;/p&gt;

&lt;p&gt;And finally, we can go back where we began: one of our Golang unit tests tries to exercise the error paths in our C++ library, resulting in a coroutine throwing an exception. The SjLj context chain — in complete disarray — causes destructors and other unwind code to run on the wrong stack during the &lt;em&gt;cleanup phase&lt;/em&gt;, corrupting our saved &lt;code&gt;context&lt;/code&gt; object for the tconcurrent event loop, which happens to fill it with zeros. Eventually tconcurrent's handler catches the exception — still on the wrong stack — and as it merrily resume normal execution, trying to switch back to the event loop, the program ends mysteriously with half its registers zeroed-out, flying off into the &lt;code&gt;null&lt;/code&gt; address.&lt;/p&gt;

&lt;p&gt;Still, some of you might be wondering, how did this ever work? How could the bug &lt;em&gt;not trigger&lt;/em&gt; when the wrong kind of object is thrown, when some optimizations are disabled, or any other of a myriad of seemingly irrelevant details?&lt;/p&gt;

&lt;p&gt;Fully explaining this is not necessary to understanding and fixing this bug, but I suspect the short answer is that this is largely due to the way the program is structured — all exceptions bubbling out of coroutines are caught in the same handler, which touches very little of the stack before switching back to the event loop's context and restoring everything saved inside. And so there is a large part of pure luck. If it just so happens that either the shuffled SjLj chain points to the right stack, or to the wrong stack but with enough buffer space to keep our saved event loop context out of harm's way, the bug will have trampled over only the stack of a terminating coroutine. If and when we manage to get back to the event loop, that coroutine's stack is never reused again, and the effect may just be invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When faced with a bottomless rabbit-hole, jumping is rarely one's idea of a good time. In the worst of cases it can be tempting to settle for the first change that makes the problem disappear, to start reworking large chunks of code until the symptoms go away, or even to abandon entirely the feature causing all this trouble.&lt;/p&gt;

&lt;p&gt;While it took the better part of a week to understand this bug, it's fair to say that we came away with a very simple diagnosis — the way our tconcurrent library uses Boost Context is incompatible with SjLj exceptions. And the cure is equally dull. We switched to Dwarf2 exceptions. But, having found the root cause, we can sleep soundly with the certainty that the bug is truly fixed, and having had the chance to learn a lot more than we bargained for!  &lt;/p&gt;

&lt;p&gt;No human is perfect, and no developer is either! As long as there is new code to write, new bugs will be written. But, at the end of the day, there is no substitute for understanding. So let's strive to learn from our errors, and make it our goal to ensure they only ever happen once. &lt;/p&gt;

</description>
      <category>cpp</category>
      <category>async</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Taking Mastodon's security to the next level - part 2: Exchange encrypted messages</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Tue, 19 Nov 2019 10:36:16 +0000</pubDate>
      <link>https://dev.to/tanker/taking-mastodon-s-security-to-the-next-level-part-2-exchange-encrypted-messages-o4c</link>
      <guid>https://dev.to/tanker/taking-mastodon-s-security-to-the-next-level-part-2-exchange-encrypted-messages-o4c</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;This is the second article in a 2-part series of blog posts that describe our endeavor to add end-to-end encryption to Mastodon: if you haven’t already, please read &lt;a href="https://dev.to/tanker/taking-mastodon-security-to-the-next-level-part-1-encrypt-your-toots-2p00"&gt;Part 1: Encrypt your toots&lt;/a&gt; first.&lt;br&gt;
In the rest of this article, we’ll refer to the Javascript code responsible for managing the UI as the &lt;em&gt;client&lt;/em&gt;, and the Ruby on Rails code as the &lt;em&gt;server&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We left on a bit of a cliffhanger - we’d managed to encrypt direct messages in the client, but hadn’t yet sent them to the server. &lt;/p&gt;

&lt;p&gt;Actually, sending encrypted messages to the server instead of plain text messages will lead to all sorts of interesting challenges and we’ll learn even more about Mastodon’s internals than we did in the first post.&lt;/p&gt;
&lt;h1&gt;
  
  
  Adding an encrypted field in the database
&lt;/h1&gt;

&lt;p&gt;Since we are encrypting only direct messages, it seems like a good idea to add an &lt;code&gt;encrypted&lt;/code&gt; boolean in the database. That way, we’ll know whether statuses are encrypted or not before attempting to decrypt them.&lt;/p&gt;

&lt;p&gt;So here’s the plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client should send an encrypted boolean to the server when calling the &lt;code&gt;api/v1/statuses&lt;/code&gt; route during the composition of direct messages&lt;/li&gt;
&lt;li&gt;The server should store the encrypted status contents in the database, along with an &lt;code&gt;encrypted&lt;/code&gt; boolean&lt;/li&gt;
&lt;li&gt;The server should send the encrypted text along with the &lt;code&gt;encrypted&lt;/code&gt; boolean back to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s write a new migration and migrate the db:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/20190913090225_add_encrypted_to_statuses.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddEncryptedToStatuses&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
      &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:statuses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:bool&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rails db:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then fix the controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/controllers/api/v1/statuses_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::V1::StatusesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PostStatusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;# ...&lt;/span&gt;
                &lt;span class="ss"&gt;encrypted: &lt;/span&gt;&lt;span class="n"&gt;status_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:encrypted&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status_params&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="c1"&gt;# ...&lt;/span&gt;
       &lt;span class="ss"&gt;:encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the controller deals only with validating the JSON request; the actual work of saving the statuses in the database is done by a service instead, so we need to patch this class as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/post_status_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostStatusService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="vi"&gt;@encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:encrypted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="c1"&gt;# …&lt;/span&gt;
    &lt;span class="n"&gt;process_status!&lt;/span&gt;


  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_status!&lt;/span&gt;
      &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status_attributes&lt;/span&gt;
    &lt;span class="c1"&gt;# Map attributes to a list of kwargs suitable for create!&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;# …&lt;/span&gt;
       &lt;span class="ss"&gt;:encrypted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="vi"&gt;@encrypted&lt;/span&gt;
   &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;compact&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s write a test to make sure the &lt;code&gt;PostStatus&lt;/code&gt; service properly persists encrypted messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/services/post_status_service_spec.rb&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'can create a new encrypted status'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fabricate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test status update"&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;text: &lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;encrypted: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_persisted&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;be_truthy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK, it passes!&lt;/p&gt;

&lt;p&gt;We can now use the new PostStatus API from the client code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/actions/compose.js&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;submitCompose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerHistory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;shouldEncrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getIn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shouldEncrypt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getIn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldEncrypt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/statuses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shouldEncrypt&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can check that this works by composing a direct message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xDV2I6En--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vj87tzvvwm61r16ycfbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xDV2I6En--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/vj87tzvvwm61r16ycfbz.png" alt="Alice sends a direct message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then checking in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rails db
# select encrypted, text from statuses order by id desc;
encrypted | text
----------+---------------------------------
 t        | A4qYtb2RBWs4vTvF8Z4fpEYy402IvfMZQqBckhOaC7DLHzw…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like it’s working as expected, so it’s time to go the other way around - sending the encrypted boolean from the server to the client.&lt;/p&gt;

&lt;h1&gt;
  
  
  Displaying encrypted messages in the UI
&lt;/h1&gt;

&lt;p&gt;This time we need to change the status serializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/serializers/rest/status_serializer.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;REST::StatusSerializer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Serializer&lt;/span&gt;
  &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:in_reply_to_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:in_reply_to_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="c1"&gt;# ...&lt;/span&gt;
             &lt;span class="ss"&gt;:encrypted&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Javascript code that fetches the status from the Rails API does not have to change.&lt;/p&gt;

&lt;p&gt;That being said, we still want to make it clear in the UI whether the message is encrypted or not - this is useful for debugging.&lt;/p&gt;

&lt;p&gt;So let’s update the &lt;code&gt;StatusContent&lt;/code&gt; component to display a padlock icon next to any encrypted message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/components/status_content.js&lt;/span&gt;
&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;encrypted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;contentHtml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;contentHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;i class="fa fa-lock" aria-hidden="true"&amp;gt;&amp;lt;/i&amp;gt;&amp;amp;nbsp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;\&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contentHtml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;contentHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contentHtml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contentHtml&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="c1"&gt;// ...&lt;/span&gt;
     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
     &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hPmsEFZ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/6txwxrnfwvsp8drivhr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hPmsEFZ---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/6txwxrnfwvsp8drivhr1.png" alt="Encrypted message with padlock"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hooray, it works! We’re ready to call &lt;code&gt;decrypt&lt;/code&gt; now.&lt;/p&gt;

&lt;h1&gt;
  
  
  Decrypt messages
&lt;/h1&gt;

&lt;p&gt;First things first, let’s patch the &lt;code&gt;TankerService&lt;/code&gt; to deal with decryption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/tanker/index.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TankerService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="nx"&gt;decrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lazyStart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fromBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’re faced with a choice. There are indeed several ways to decrypt statuses in the client code. For simplicity’s sake, we’ll patch the &lt;code&gt;processStatus&lt;/code&gt; function which is called for each message returned from the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/actions/importer/index.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;processStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// …&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// `content` as returned by the server has a &amp;lt;p&amp;gt; around it, so&lt;/span&gt;
    &lt;span class="c1"&gt;// clean that first&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;`&lt;/span&gt;
    &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updateStatusContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clearHtml&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we call an &lt;code&gt;udpateStatusContent&lt;/code&gt; action to update the status after it has been decrypted.&lt;/p&gt;

&lt;p&gt;I won’t go through the implementation of the &lt;code&gt;updateStatusContent&lt;/code&gt; action and reducers as they’re pretty standard. &lt;/p&gt;

&lt;p&gt;Anyway, we can check that our patch works by logging in as Alice, and then sending a message to ourselves:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lGOwSm1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hv5crikyilsfoyy92btu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lGOwSm1v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hv5crikyilsfoyy92btu.png" alt="Alice sees her own encrypted direct message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Exchanging private messages
&lt;/h1&gt;

&lt;p&gt;Being able to send encrypted messages to oneself is quite impressive, but I don’t think we should stop there :)&lt;/p&gt;

&lt;p&gt;Let’s create a new account for Bob, and look at what happens when Alice sends a message containing &lt;code&gt;@bob&lt;/code&gt; - this is known as a &lt;em&gt;mention&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KZsi0STK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/spwo5debkgbevjilehh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KZsi0STK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/spwo5debkgbevjilehh0.png" alt="Alice to Bob"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Normally, Bob should get a notification because he was sent a direct message, but this is not the case.&lt;/p&gt;

&lt;p&gt;Clearly there is something to fix there.&lt;/p&gt;

&lt;p&gt;After digging into the code, here's what I found out: notifications about direct messages are generated by a class named &lt;code&gt;ProcessMentionsService&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here’s the relevant part of the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessMentionsService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MENTION_RE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="n"&gt;mentionned_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
         &lt;span class="c1"&gt;# …&lt;/span&gt;
         &lt;span class="n"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;  &lt;span class="p"&gt;\\&lt;/span&gt;
           &lt;span class="n"&gt;mentionned_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mentions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first_or_create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;states&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="k"&gt;end&lt;/span&gt;

       &lt;span class="n"&gt;mentions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that the server looks for &lt;code&gt;@&lt;/code&gt; mentions in the status text using regular expression matches and then builds a list of Mention instances.&lt;/p&gt;

&lt;p&gt;Then something interesting happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/process_mentions_services.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessMentionsService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
   &lt;span class="c1"&gt;# …&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mentioned_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mentioned_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;local?&lt;/span&gt;
      &lt;span class="no"&gt;LocalNotificationWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;mentioned_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="n"&gt;mentioned_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activitypub?&lt;/span&gt;
       &lt;span class="no"&gt;ActivityPub&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DeliveryWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;activitypub_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
         &lt;span class="n"&gt;mention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
         &lt;span class="n"&gt;mentioned_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inbox_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the server triggers a task from the &lt;code&gt;LocalNotificationWorker&lt;/code&gt; if the mentioned account is local to the instance. It turns out this will later use the websocket server we discovered in Part 1 to send a notification to the client.&lt;/p&gt;

&lt;p&gt;Side note here: if the mentioned account is &lt;em&gt;not&lt;/em&gt; local to the instance, an Activity Pub delivery worker is involved. This is at the heart of the Mastodon mechanism: each instance can either send messages across local users, or they can use the ActivityPub protocol to send notifications across to another instance.&lt;/p&gt;

&lt;p&gt;Back to the task at hand: it’s clear now that if the status is encrypted by the time it’s processed by the server, nothing will match and no notification will be created. That’s why Bob didn’t get any notification when we tried sending a direct message from Alice to Bob earlier.&lt;/p&gt;

&lt;p&gt;Thus we need to process the &lt;code&gt;@&lt;/code&gt; mentions client-side, then send a list of mentions next to the encrypted status to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//app/javascript/mastodon/actions/compose.js&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;submitCompose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerHistory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mentionsSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldEncrypt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Parse mentions from the status&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/@&lt;/span&gt;&lt;span class="se"&gt;(\S&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// We want the first group, without the leading '@'&lt;/span&gt;
      &lt;span class="nx"&gt;mentionsSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mentionsSet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/statuses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mentions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we did for the &lt;code&gt;encrypted&lt;/code&gt; boolean, we have to allow the &lt;code&gt;mentions&lt;/code&gt; key in the statuses controller and forward the &lt;code&gt;mentions&lt;/code&gt; array to the &lt;code&gt;PostStatus&lt;/code&gt; service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::v1::StatusesController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Api&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BaseController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;status_params&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="ss"&gt;:encypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;mentions: &lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PostStatusService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                         
                &lt;span class="ss"&gt;encrypted: &lt;/span&gt;&lt;span class="n"&gt;status_param&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:encrypted&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="ss"&gt;mentions: &lt;/span&gt;&lt;span class="n"&gt;status_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:mentions&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;PostStatus&lt;/code&gt; service we forward the mentions to the &lt;code&gt;ProcessMentions&lt;/code&gt; service using a &lt;code&gt;username&lt;/code&gt; key in an option hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/post_status_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostStatusService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_status!&lt;/span&gt;
    &lt;span class="n"&gt;process_mentions_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;usernames: &lt;/span&gt;&lt;span class="vi"&gt;@mentions&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, finally, in the &lt;code&gt;ProcessMentions&lt;/code&gt; service, we convert usernames into real accounts and create the appropriate mentions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/process_mentions_service.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProcessMentionsService&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;BaseService&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypted?&lt;/span&gt;
      &lt;span class="n"&gt;usernames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:usernames&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="n"&gt;usernames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;username: &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Mention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="vi"&gt;@status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="ss"&gt;:account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
   &lt;span class="k"&gt;else&lt;/span&gt;
     &lt;span class="c1"&gt;# same code as before&lt;/span&gt;
   &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can try encrypting the following status: &lt;code&gt;@bob I have a secret message for you&lt;/code&gt; and check that Bob gets the notification.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--42tQlJsD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/es5ki83vcjivadekopyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--42tQlJsD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/es5ki83vcjivadekopyh.png" alt="Notification for Bob"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But when Bob tries to decrypt Alice’s message, it fails with a &lt;code&gt;resource ID not found&lt;/code&gt; error message: this is because Alice never told &lt;em&gt;Tanker&lt;/em&gt; that Bob had access to the encrypted message. &lt;/p&gt;

&lt;p&gt;For Bob to see a message encrypted by Alice, Alice must provide Bob’s public identity when encrypting the status. We still have some code to write, because in Part 1 we created and stored only private tanker identities. Luckily, the &lt;code&gt;tanker-identity&lt;/code&gt; Ruby gem contains a &lt;code&gt;get_public_identity&lt;/code&gt; function to convert private identities to public ones.&lt;/p&gt;

&lt;p&gt;So the plan becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a helper function to access public identities from rails&lt;/li&gt;
&lt;li&gt;When rendering the initial-state from the server, add public identities to the serialized accounts.&lt;/li&gt;
&lt;li&gt;In the client code, fetch public identities of the recipients of the encrypted statuses&lt;/li&gt;
&lt;li&gt;Instead of calling &lt;code&gt;encrypt&lt;/code&gt; with no options, call &lt;code&gt;tanker.encrypt( resource, { shareWithUsers: identities })&lt;/code&gt; where &lt;code&gt;identities&lt;/code&gt; is an array of public identities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good thing we are already parsing the &lt;code&gt;@&lt;/code&gt; mentions client-side :)&lt;/p&gt;

&lt;h1&gt;
  
  
  Sending public identities in the initial state
&lt;/h1&gt;

&lt;p&gt;First we adapt our &lt;code&gt;TankerIdentity&lt;/code&gt; class so we can convert a private identity to a public one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/lib/tanker_identity.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_public_identity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;private_identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Tanker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_public_identity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;private_identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we add the &lt;code&gt;tanker_public_identity&lt;/code&gt; attribute to the &lt;code&gt;User&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tanker_public_identity&lt;/span&gt;
    &lt;span class="no"&gt;TankerIdentity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get_public_identity&lt;/span&gt; &lt;span class="n"&gt;tanker_identity&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tell the &lt;code&gt;Account&lt;/code&gt; class to delegate the &lt;code&gt;tanker_public_identity&lt;/code&gt; method to the inner &lt;code&gt;user&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/use.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:unconfirmed_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:current_sign_in_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;:current_sign_in_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="o"&gt;...&lt;/span&gt;
           &lt;span class="ss"&gt;:tanker_public_identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;prefix: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We adapt the account serializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/serializers/rest/account_serializer.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;REST::AccountSerializer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Serializer&lt;/span&gt; 
   &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
              &lt;span class="c1"&gt;# ...:&lt;/span&gt;
              &lt;span class="ss"&gt;:tanker_public_identity&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tanker_public_identity&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_tanker_public_identity&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now the client can access the Tanker public identities of the mentioned accounts in the initial state.&lt;/p&gt;

&lt;h1&gt;
  
  
  Sharing encrypted messages
&lt;/h1&gt;

&lt;p&gt;We can now collect the identities from the state and use them in the call to &lt;code&gt;tanker.encrypt()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;submitCompose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerHistory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;identities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;knownAccounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getIn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accounts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nx"&gt;toJS&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;knownAccounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;knownAccounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mentionsSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;identities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker_public_identity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// …&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;shareWithUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;identities&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/statuses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s see what happens after this code change. This time, when Bob clicks on the notification, he sees Alice's decrypted message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p0p_NsAs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/sj36o25jkqeitn7rdx41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p0p_NsAs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/sj36o25jkqeitn7rdx41.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done!&lt;/p&gt;

&lt;h1&gt;
  
  
  What did we learn?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;We discovered how notifications are handled in Mastodon&lt;/li&gt;
&lt;li&gt;We found out that some server-side processing needed to be moved client-side, as is expected when client-side encryption is used.&lt;/li&gt;
&lt;li&gt;We implemented a fully working end-to-end encryption feature for Mastodon’s direct messages, making sure direct message can be read only by the intended recipients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are curious, here are some statistics about the number of changes we had to write, excluding generated files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git diff --stat \
   :(exclude)yarn.lock \
  :(exclude)Gemfile.lock \
  :(exclude)db/schema.rb
 41 files changed, 360 insertions(+), 40 deletions(-)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Future Work
&lt;/h1&gt;

&lt;p&gt;Reminder: this is a proof of concept, and many things could be improved. Here’s a list of problems and hints about their solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improve status decryption
&lt;/h2&gt;

&lt;p&gt;We are violating an implicit property of the messages in Mastodon: they are supposed to be immutable, as shown by the fact that until our patch, no action was able to change the contents of the statuses.&lt;/p&gt;

&lt;p&gt;We probably would have to refactor the client code a bit to not violate this property, with the added benefit that the UI will no longer “flicker” when statuses go from encrypted base64 strings to clear text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving the identity verification flow
&lt;/h2&gt;

&lt;p&gt;We should remove the  &lt;code&gt;@tanker/verification-ui&lt;/code&gt; package and instead introduce tanker identity verification inside the existing authentication flow.&lt;/p&gt;

&lt;p&gt;You can check out the &lt;a href="https://docs.tanker.io/core/latest/guide/start/"&gt;Starting a Tanker session&lt;/a&gt; section of Tanker’s documentation for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provide alternative verification methods
&lt;/h2&gt;

&lt;p&gt;You may have noticed that the identity verification currently works by having Tanker and Mastodon servers holding some secrets. Also, the email provider of the users can, in theory, intercept the email containing the verification code.&lt;/p&gt;

&lt;p&gt;If this concerns you, please note that instead of using email-based verification, we could use another verification method called the verification key. You can read more about that in the &lt;a href="https://docs.tanker.io/core/latest/tutorials/alternative-verification-methods/"&gt;Alternative verification methods&lt;/a&gt; section of the Tanker documentation.&lt;/p&gt;

&lt;p&gt;Please do note that in this case, users are in charge of their verification key and will not be able to access any of their encrypted resources if they lose it. &lt;/p&gt;

&lt;p&gt;We could implement both verification methods and let users choose between the two during onboarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement pre-registration sharing
&lt;/h2&gt;

&lt;p&gt;The code assumes all users sending or receiving direct messages already have a Tanker identity registered. This can also be solved by using a Tanker feature called &lt;a href="https://docs.tanker.io/core/latest/tutorials/pre-registration-sharing/"&gt;Pre-registration sharing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make encryption work across instances
&lt;/h2&gt;

&lt;p&gt;Finally, our implementation works only if the sender and receiver of the direct messages are on the same instance - we need to make encryption work with the ActivityPub protocol.&lt;/p&gt;

&lt;p&gt;I have a few ideas but fixing it seems non-trivial. Still, it would make for a pretty nice challenge :)&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Thanks for reading this far. Writing the patch was a nice experience: Mastodon’s source code is clean and well-organized. You can browse the changes on the &lt;a href="https://github.com/tootsuite/mastodon/pull/12428"&gt;pull request on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope this gave you an idea of the possibilities offered by Tanker. If you’d like to use Tanker in your own application, please &lt;a href="https://tanker.io/"&gt;get in touch with us&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to leave a comment below and give us your feedback!&lt;/p&gt;

</description>
      <category>mastodon</category>
      <category>javascript</category>
      <category>endtoend</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Taking Mastodon security to the next level - part 1: Encrypt your toots</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Tue, 22 Oct 2019 14:13:38 +0000</pubDate>
      <link>https://dev.to/tanker/taking-mastodon-security-to-the-next-level-part-1-encrypt-your-toots-2p00</link>
      <guid>https://dev.to/tanker/taking-mastodon-security-to-the-next-level-part-1-encrypt-your-toots-2p00</guid>
      <description>&lt;h1&gt;
  
  
  What is this about?
&lt;/h1&gt;

&lt;p&gt;My name is Dimitri Merejkowsky and I’ve been working at Tanker since June 2016. We’re a software company whose goal is to make end-to-end encryption simple. (More details on &lt;a href="https://tanker.io"&gt;our website&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I’ve been an enthusiastic user of &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt; since April 2017. One thing that always bugs me is that Mastodon administrators have access to everything about their users, as we'll see in a minute.&lt;/p&gt;

&lt;p&gt;A few weeks ago, I decided to tackle this issue and try to encrypt Mastodon's direct messages with Tanker. &lt;/p&gt;

&lt;p&gt;And that's how this series of articles was born. They’re written as something in between a tutorial and a story. You can use it to follow in my footsteps or to just enjoy the ride and have a good read: we'll discover what it actually means to implement Tanker in an existing solution and learn a few things about Mastodon’s internals. If you're curious, you can also jump to the end result &lt;a href="https://github.com/tootsuite/mastodon/pull/12428"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But first, let's go back to the problem that triggered the whole thing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction - What's wrong with Mastodon's direct messages?
&lt;/h1&gt;

&lt;p&gt;Let's assume there is a Mastodon instance running with 3 users: Alice, Bob, and Eve.&lt;/p&gt;

&lt;p&gt;First, Alice decides to send a direct message to Bob. She doesn't want her, or Bob’s, followers to see it, so she selects "Direct" visibility in the drop-down menu before sending her message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BG53E4Oc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hn0l01pmkgddwctqkplt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BG53E4Oc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hn0l01pmkgddwctqkplt.png" alt="Alice sends a DM to Bob"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the message is sent, she can see it the Direct messages column:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mSD8rAuQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gw1chtddpkp4ea8lc50v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mSD8rAuQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/gw1chtddpkp4ea8lc50v.png" alt="Alice views message to Bob"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bob, on the other hand, gets a notification and Alice's message appears in his column:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0keL0L-R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/c2r6mvrhl0yhpdo4vvob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0keL0L-R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/c2r6mvrhl0yhpdo4vvob.png" alt="Bob views message from Alice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, Eve does not get any notification, and if she tries to access the message directly using the permalink, she gets a 404 error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XavqmC-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/m1lov0vqn9yh77y1lpe4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XavqmC-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/m1lov0vqn9yh77y1lpe4.png" alt="Eve does not see Alice's message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first glance, it looks as if the feature is working - only Bob can see Alice's message.&lt;/p&gt;

&lt;p&gt;But, alas, the Mastodon admins can still read it because they have access to the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# select text from statuses order by id desc;
        text
-----------------
 @bob hello!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The aim of this series
&lt;/h1&gt;

&lt;p&gt;In this series of articles, I would like to invite you to follow the steps I took to implement end-to-end encryption for direct messages on Mastodon. Note that I'm using Debian 10; your mileage may differ if you’re using a different distribution or another operating system.&lt;/p&gt;

&lt;p&gt;When we're done, here's what we'll have:&lt;/p&gt;

&lt;p&gt;Nothing will change from Alice's point of view when composing the direct message.&lt;/p&gt;

&lt;p&gt;Bob will still see Alice's message, but this time there will be a lock to signify it’s encrypted:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i2KJqSfD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/d75f69odzygh1mer0c76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i2KJqSfD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/d75f69odzygh1mer0c76.png" alt="Bob sees encrypted message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the admins will no longer be able to read all the messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# select encrypted, text from statuses order by id desc;
encrypted | text
----------+---------------------------------
 t        | A4qYtb2RBWs4vTvF8Z4fpEYy402IvfMZQqBckhOaC7DLHzw
 f        | @bob hello!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sounds interesting? Let's dive in!&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting started
&lt;/h1&gt;

&lt;p&gt;We are going to make some changes in Mastodon's source code, so let's clone it and make sure we can run an instance on our development machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git://github.com/tootsuite/mastodon
&lt;span class="nb"&gt;cd &lt;/span&gt;mastodon
&lt;span class="c"&gt;# install all required libraries:&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;Aptfile | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="c"&gt;# Install correct ruby version with rvm&lt;/span&gt;
rvm &lt;span class="nb"&gt;install &lt;/span&gt;ruby-2.6.1
&lt;span class="c"&gt;# Install all ruby dependencies&lt;/span&gt;
bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# Install all Javascript dependencies&lt;/span&gt;
yarn
&lt;span class="c"&gt;# Run all processes with foreman&lt;/span&gt;
foreman start &lt;span class="nt"&gt;-f&lt;/span&gt; Procfile.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can open the &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; URL in a browser and sign up our first user.&lt;/p&gt;

&lt;p&gt;The "vanilla" Mastodon is running as expected, so we can start changing the code and see what happens :)&lt;/p&gt;

&lt;h1&gt;
  
  
  Calling encrypt() the naive way
&lt;/h1&gt;

&lt;p&gt;In the  &lt;a href="https://docs.tanker.io/core/latest/api/tanker/"&gt;API section&lt;/a&gt; of the Tanker documentation, we notice there’s an  encrypt() function in a Tanker object. We also see a bit of code that tells us how to instantiate Tanker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-app-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tanker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Tanker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need an App ID, so let's create an application in the &lt;a href="https://dashboard.tanker.io/"&gt;Tanker Dashboard&lt;/a&gt; and patch the front-end code directly, without thinking too much about the implications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In app/javascript/mastodon/actions/compose.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;submitCompose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerHistory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;our-app-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tanker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Tanker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getIn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PreconditionFailed: Expected status READY but got STOPPED trying to encrypt.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After digging in the documentation, it turns out we need to &lt;em&gt;start a session&lt;/em&gt; first.&lt;/p&gt;

&lt;p&gt;If you’re wondering, here's why: Tanker implements an end-to-end protocol and thus encryption occurs on the users' devices. To that end, it uses an &lt;em&gt;Encrypted Local Storage&lt;/em&gt; (containing some private keys, among other things) which can be accessed only when a Tanker session has been started.&lt;/p&gt;

&lt;p&gt;The doc also says we need to &lt;em&gt;verify&lt;/em&gt; users’ identities before starting a Tanker session, and that Tanker identities must be generated and stored on the application server - in our case, the Ruby on Rails code from the Mastodon project.&lt;/p&gt;

&lt;p&gt;That means that we can’t do everything client-side in Javascript; we also need to modify the server as well as figuring out how these two communicate with each other.&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting to know the architecture
&lt;/h1&gt;

&lt;p&gt;The &lt;a href="https://docs.joinmastodon.org/development"&gt;Mastodon development guide&lt;/a&gt; contains an overview of the Mastodon architecture. Here are the relevant parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A rails server is in charge of handling authentication (through the &lt;a href="https://github.com/plataformatec/devise"&gt;Devise gem&lt;/a&gt; and serving web pages&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/tootsuite/mastodon/blob/master/streaming/index.js"&gt;Node.js WebSocket server&lt;/a&gt; is used for refreshing the user timeline, pushing notifications and the like&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/tootsuite/mastodon/tree/master/app/javascript/mastodon"&gt;React application&lt;/a&gt; using &lt;a href="https://redux.js.org/"&gt;Redux&lt;/a&gt; to manage the state shows the main UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To understand how the Ruby and the Javascript codes cooperate we can look at the HTML source of the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- .. --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;”initial-state”,&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;”application/json”&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;....&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice@tanker.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That page is generated by Rails. The React app parses this HTML, extracts its initial state from the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element, and starts from there.&lt;br&gt;
Note that the initial state contains a JSON object under the &lt;code&gt;meta&lt;/code&gt; key.&lt;br&gt;
The meta object contains (among other things):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An  access token for the WebSocket server&lt;/li&gt;
&lt;li&gt;The email of the current user&lt;/li&gt;
&lt;li&gt;The ID of the current user in the database (under the me key)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, here's the plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We'll generate a Tanker identity server-side&lt;/li&gt;
&lt;li&gt;Put it inside the initial state&lt;/li&gt;
&lt;li&gt;Fetch it from the initial state and start a Tanker session&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Generating Tanker Identities
&lt;/h1&gt;

&lt;p&gt;First, add the Tanker App Id and secret into the &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;(The Tanker app secret must not be checked in along with the rest of the source code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;TANKER_APP_ID &lt;span class="o"&gt;=&lt;/span&gt; &amp;lt;the-app-id&amp;gt;
TANKER_APP_SECRET &lt;span class="o"&gt;=&lt;/span&gt; &amp;lt;the-ap-secret&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we create a new file named &lt;code&gt;app/lib/tanker_identity.rb&lt;/code&gt; containing this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;TankerIdentity&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Tanker&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_identity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TANKER_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"TANKER_APP_SECRET"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We adapt the &lt;code&gt;User&lt;/code&gt; model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/users.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;

  &lt;span class="n"&gt;after_create&lt;/span&gt; &lt;span class="ss"&gt;:set_tanker_identity&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_tanker_identity&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tanker_identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TankerIdentity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_identity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_attribute&lt;/span&gt; &lt;span class="ss"&gt;:tanker_identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tanker_identity&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We write a migration and then migrate the DB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# db/migrate/20190909112533_add_tanker_identities_to_users.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddTankerIdentitiesToUsers&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_column&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tanker_identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rails db:setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we write a new test for the &lt;code&gt;AppSignUpService&lt;/code&gt; and run the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/services/app_sign_up_service_spec.rb&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'creates a user with a Tanker identity'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;good_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource_owner_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tanker_identity&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_notbe_nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rspec
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;Finished in 3 minutes 49.4 seconds (files took 8.56 seconds to load)
2417 examples, 0 failure
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They pass! We now have Tanker identities generated server-side. Let's use them to start a Tanker session.&lt;/p&gt;

&lt;h1&gt;
  
  
  Starting a Tanker session
&lt;/h1&gt;

&lt;p&gt;When starting a Tanker session you need to verify the identity of the user. This involves sending an email and entering an 8-digit code - that's how you can be sure that you’re sharing encrypted data with the correct user.&lt;/p&gt;

&lt;p&gt;As a shortcut, Tanker provides a &lt;code&gt;@tanker/verfication-ui&lt;/code&gt; package containing a ready-to-use UI to handle identity verification using emails.&lt;/p&gt;

&lt;p&gt;It's used like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tanker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Tanker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verificationUI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;VerificationUI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tanker&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;verificationUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need the app ID, the Tanker identity and the email to start a Tanker session, so let's make sure they appear in the aforementioned &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/helpers/application_helper.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_initial_state&lt;/span&gt;
  &lt;span class="n"&gt;state_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_signed_in?&lt;/span&gt;
    &lt;span class="n"&gt;state_params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tanker_identity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tanker_identity&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/presenters/initial_state_presenter.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InitialStatePresenter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveModelSerializers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Model&lt;/span&gt;
  &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="ss"&gt;:settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:push_subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
             &lt;span class="c1"&gt;# ...&lt;/span&gt;
             &lt;span class="ss"&gt;:tanker_identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:tanker_app_id&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/serializers/initial_state_serializer.rb&lt;/span&gt;
&lt;span class="nb"&gt;require_relative&lt;/span&gt; &lt;span class="s2"&gt;"../../lib/tanker"&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InitialStateSerializer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveModel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Serializer&lt;/span&gt;
  &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="ss"&gt;:meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:compose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tanker_identity&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tanker_identity&lt;/span&gt;
  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:tanker_app_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TANKER_APP_ID&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we fetch our values from the &lt;code&gt;initial_state.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/initial_state.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tankerIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tanker_identity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tankerAppId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tanker_app_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Creating a Tanker service
&lt;/h1&gt;

&lt;p&gt;The challenge now becomes: how and when do we call &lt;code&gt;verificationUI.start()&lt;/code&gt;, knowing that it will display a big pop-up and hide the rest of the UI?&lt;/p&gt;

&lt;p&gt;After a bit of thinking, we decide to wrap calls to &lt;code&gt;tanker.encrypt()&lt;/code&gt;, &lt;code&gt;tanker.decrypt()&lt;/code&gt; and &lt;code&gt;verificationUI.starte()&lt;/code&gt; in a &lt;code&gt;TankerService&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TankerService&lt;/code&gt; class will be responsible for ensuring the tanker session is started right before data is encrypted or decrypted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/tanker/index.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fromBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Tanker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanker/client-browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;VerificationUI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanker/verification-ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TankerService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tankerIdentity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tankerAppId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tankerIdentity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tankerIdentity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Tanker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tankerAppId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verificationUI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;VerificationUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;encrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lazyStart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;decrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lazyStart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fromBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encryptedData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;clearText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lazyStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;Tanker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STOPPED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startPromise&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verificationUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tankerIdentity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startPromise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we configure &lt;a href="https://github.com/reduxjs/redux-thunk"&gt;Redux thunk middleware&lt;/a&gt; to take the TankerService as&lt;br&gt;
extra argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/store/configureStore.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;thunkMiddleWare&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redux-thunk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tankerIdentity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;tankerAppId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../initial_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TankerService&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../tanker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TankerService&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tankerIdentity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tankerAppId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;thunkMiddleWare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withExtraArgument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;configureStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;createStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;applyMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;thunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this change, the thunk middleware allows us to access the &lt;code&gt;TankerService&lt;/code&gt; instance from any Redux action.&lt;/p&gt;

&lt;p&gt;So, now we can adapt the &lt;code&gt;submitCompose&lt;/code&gt; action properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/javascript/mastodon/actions/compose.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;submitCompose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routerHistory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getIn&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;privacy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldEncrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visibility&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;direct&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shouldEncrypt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encryptedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tankerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;about to send encrypted text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encryptedText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submitComposeRequest&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1/statuses&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ...,&lt;/span&gt;
      &lt;span class="nx"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we're done, we get those pop-ups showing us that the verification process worked:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jO8Draj2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/opweky5tksr2zod3llkq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jO8Draj2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/opweky5tksr2zod3llkq.png" alt="Identity verification prompt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AIjKruHa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cqkzxouz7zu5swfl2rm3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AIjKruHa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/cqkzxouz7zu5swfl2rm3.png" alt="Identity verified"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And some logs indicating the status was indeed encrypted&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting verification UI ...
Verification UI started
About to send  encrypted text: AxMXSEhEnboU732MUc4tqvOmJECocd+fy/lprQfpYGSggJ28
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all for Part 1. We can now create and verify cryptographic identities of all users in our local instance, use them to start a Tanker session, and encrypt our direct messages.&lt;/p&gt;

&lt;p&gt;But how will the server actually handle those encrypted messages?&lt;/p&gt;

&lt;p&gt;Stay tuned for part 2!&lt;/p&gt;

&lt;p&gt;Follow Tanker on dev.to or on &lt;a href="https://twitter.com/tanker"&gt;twitter&lt;/a&gt; to be notified when the next part is published - and feel free to ask questions in the comments section below.&lt;/p&gt;

</description>
      <category>mastodon</category>
      <category>javascript</category>
      <category>endtoend</category>
      <category>showdev</category>
    </item>
    <item>
      <title>🚀 We've just launched FileKit on ProductHunt!</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Thu, 19 Sep 2019 12:18:52 +0000</pubDate>
      <link>https://dev.to/tanker/we-ve-just-launched-filekit-on-producthunt-2e9m</link>
      <guid>https://dev.to/tanker/we-ve-just-launched-filekit-on-producthunt-2e9m</guid>
      <description>&lt;p&gt;Hello everyone,&lt;/p&gt;

&lt;p&gt;&lt;a href="(https://dev.to/tanker/we-created-filekit-the-end-to-end-encrypted-cloud-storage-service-3hbf)"&gt;Last week&lt;/a&gt; we told you about our latest product: an end-to-end encrypted file transfer toolkit for Javascript.&lt;/p&gt;

&lt;p&gt;Today we're thrilled to announce the &lt;a href="https://www.producthunt.com/posts/filekit-by-tanker"&gt;launch of FileKit on Product Hunt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We wish to empower you, fellow developers, with the technology to build end-to-end encrypted applications.&lt;/p&gt;

&lt;p&gt;FileKit comes with the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload, download and share files up 2GB&lt;/li&gt;
&lt;li&gt;Simple and modern API&lt;/li&gt;
&lt;li&gt;Easy to integrate in your app&lt;/li&gt;
&lt;li&gt;Builtin UI component for user’s identity verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of a file transfer application built with FileKit: &lt;a href="https://share.tanker.io/"&gt;https://share.tanker.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go check it out and tell us what you think!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>filetransfer</category>
    </item>
    <item>
      <title>We created FileKit - The end-to-end encrypted file transfer toolkit for Javascript
</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Wed, 11 Sep 2019 13:57:16 +0000</pubDate>
      <link>https://dev.to/tanker/we-created-filekit-the-end-to-end-encrypted-cloud-storage-service-3hbf</link>
      <guid>https://dev.to/tanker/we-created-filekit-the-end-to-end-encrypted-cloud-storage-service-3hbf</guid>
      <description>&lt;p&gt;Nowadays, many applications use cloud storage providers such as Amazon S3 or Microsoft Azure. These services are very convenient; however, they have not yet cracked the data protection issue as the responsibility for data security is thrown right back at their clients. Here is a list of their security offerings and their shortcomings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Encryption in transit: it is the “S” in https, and means that the data is encrypted while it is in transit, yet is vulnerable as soon as it arrives on the server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Encryption at rest: this means that the cloud storage provider encrypts the data on the hard drives on their storage servers. Whether the keys used for encryption are stored on their storage service or on your servers, the threat model is largely the same. You must trust the infrastructure that runs your servers and, if it gets compromised, an attacker will be able to dump all the encrypted data and the key to decrypt them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two techniques protect the data: between the client and the server and between the server and the storage server, but the data is still vulnerable as it is unprotected on the server itself.&lt;/p&gt;

&lt;p&gt;Trust is a personal choice; your users shouldn't have to trust you for your good technical choices. An ideal solution should be able to protect your app from data leaks such that if someone gets access to your server, they will not be able to decrypt your users' data. This solution would relieve you from these trust issues while keeping all of the advantages provided by cloud service providers.&lt;/p&gt;

&lt;p&gt;End-to-end encryption is a solution where the data is encrypted on the client and the encryption keys stay on the client’s devices. With this solution, your servers don't see any clear user data. As such, they will not be able to leak that user data if they ever get compromised.&lt;/p&gt;

&lt;p&gt;The main issue becomes that only the device that has encrypted the data is able to decrypt it. So you will need to exchange securely the encryption keys between devices to allow a user to access their data on all devices, and to allow a user to share their data with other users. This is non-trivial as we cannot expect users to handle their keys themselves.&lt;/p&gt;

&lt;p&gt;After a few years of building solutions to help developers secure data exchanges in their apps, we at Tanker designed a new product solving an array of common use cases with cloud storage providers. This product implements end-to-end encryption and solves the key exchange problem to keep the user experience pleasant while enabling you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Securely share files&lt;/strong&gt; with individuals both inside and outside your organization. Files can be read only by the intended recipient. Allow secure collaboration on files in critical domains: medical, legal, journalistic, contractual etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Meet regulatory and policy requirements&lt;/strong&gt;. Enhance trust by guaranteeing your customers’ privacy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus on your business logic&lt;/strong&gt;. It is straightforward and easy to implement and you won’t have to handle the cryptography at all.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FileKit: What is it?
&lt;/h2&gt;

&lt;p&gt;FileKit is a secure cloud storage service that handles all cryptography and key exchanges seamlessly, letting you focus on what's important.&lt;/p&gt;

&lt;p&gt;FileKit comes with the following features:&lt;/p&gt;

&lt;p&gt;Upload, download and share files up 2GB&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use end-to-end encryption&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easy to integrate in your app&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Builtin UI component for user’s identity verification&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example of a file transfer application built with FileKit: &lt;a href="https://tankerhq.github.io/filekit-tuto-app/"&gt;https://tankerhq.github.io/filekit-tuto-app/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get started with &lt;a href="https://docs.tanker.io/filekit/latest/tutorials/file-transfer/"&gt;our file transfer tutorial&lt;/a&gt;, or have a look at the &lt;a href="https://docs.tanker.io/filekit/latest/"&gt;documentation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;PS: This article was originally written by &lt;a href="https://github.com/banetl"&gt;Loïc Banet&lt;/a&gt;. It's been reproduced it here to give you a chance to see it in your notifications feed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>endtoend</category>
      <category>encryption</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Reacting to Dropbox: another take on cross-platform C++ development</title>
      <dc:creator>Théo DELRIEU</dc:creator>
      <pubDate>Wed, 04 Sep 2019 15:46:06 +0000</pubDate>
      <link>https://dev.to/tanker/reacting-to-dropbox-another-take-on-cross-platform-c-development-42o0</link>
      <guid>https://dev.to/tanker/reacting-to-dropbox-another-take-on-cross-platform-c-development-42o0</guid>
      <description>&lt;p&gt;A few weeks ago, Dropbox announced in a blog post that they had decided to drop C++ from their codebase. This article sparked off a number of reactions, especially on the C++ subreddit.&lt;/p&gt;

&lt;p&gt;I suggest that you read &lt;a href="https://blogs.dropbox.com/tech/2019/08/the-not-so-hidden-cost-of-sharing-code-between-ios-and-android/"&gt;that&lt;/a&gt; first, but here is a TL;DR:&lt;/p&gt;

&lt;p&gt;In 2013 Dropbox started to use C++ to share mobile code between iOS and Android, the goal being to avoid having to write it twice. 6 years later, they made a U-turn because of the overhead of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Custom frameworks and libraries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A customized development environment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Differences between Mobile platforms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finding and keeping experienced C++ developers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are all valid points. At &lt;a href="https://tanker.io/"&gt;Tanker&lt;/a&gt;, we had to face similar challenges with our cross-platform end-to-end encryption SDK. However, we took a different approach to sharing C++ code between multiple platforms efficiently, including iOS and Android; this worked well for us.&lt;/p&gt;

&lt;p&gt;I’ll share my thoughts about the Dropbox announcement and then present the solutions that we adopted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remaining obstacles
&lt;/h2&gt;

&lt;p&gt;It is undeniable that having cross-platform C++ code is no easy feat, and implies several kinds of overhead.&lt;/p&gt;

&lt;p&gt;Recruiting can be a real pain, especially when requiring expertise in C++, cross-building, and mobile development. Also, internal training is costly, and having trained people leaving a few months after they have been trained is a risk you might not want to take.&lt;/p&gt;

&lt;p&gt;Writing and maintaining language-specific bindings is challenging, having N targeted platforms usually implies having N bindings, debugging C++ code through Java can be nightmarish, not to mention the complications of cross-building.&lt;/p&gt;

&lt;p&gt;These issues existed when Dropbox started their C++ mobile journey in 2013, and still persist today.&lt;/p&gt;

&lt;h2&gt;
  
  
  An evolving environment
&lt;/h2&gt;

&lt;p&gt;Fortunately, many other things have improved since. The number of active open-source Github projects has globally increased. The number of projects written in C++ has increased by a factor of 7.64 between 2013 and 2018:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mregRmfG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ag223aORumUEmQQazXKpmVw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mregRmfG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Ag223aORumUEmQQazXKpmVw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I disagree with Dropbox when they claim that &lt;em&gt;the easiest overhead to predict with C++ is the need to build frameworks and libraries&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I do not know how many C++ JSON libraries there were when they decided to write &lt;a href="https://github.com/dropbox/json11"&gt;json11&lt;/a&gt;, but there are around 15 listed by &lt;a href="https://en.cppreference.com/w/cpp/links/libs"&gt;isocpp&lt;/a&gt; at present. Any recent project should consider using one of those instead of &lt;a href="https://xkcd.com/927/"&gt;hand-rolling one&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The C++ ecosystem has evolved greatly, and we are beginning to have a wide choice of open-source libraries to solve the usual development problems.&lt;/p&gt;

&lt;p&gt;C++ cross-build support has also improved since 2013, when companies often had to write the tools themselves:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We needed a custom build system that created libraries containing C++ code as well as Java and Objective-C wrappers and could generate targets that were understood by both Xcodebuild and Gradle.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nowadays, both Android NDK and Xcode ship with recent C++ compilers (especially the former). A tool like CMake can generate Xcode projects, and can be &lt;a href="https://developer.android.com/ndk/guides/cmake#usage"&gt;invoked directly from Gradle&lt;/a&gt;. There is still some boilerplate to write of course, but still less than creating a whole build system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-platform development at Tanker
&lt;/h2&gt;

&lt;p&gt;As I mentioned before, &lt;a href="https://tanker.io/"&gt;Tanker&lt;/a&gt; builds an end-to-end encryption SDK on which our other products are based. There are two versions, the first in JavaScript, the second in C++. The latter is used in our iOS/Android SDKs, and also runs on Linux/Windows/macOS. From the start, we had the same goal as Dropbox: sharing as much C++ code between platforms as possible. &lt;/p&gt;

&lt;p&gt;Having a small team of engineers, we cannot spend time reimplementing libraries (HTTP, cryptography, formatting, etc…) ourselves, unless we really have to. This is why we use and contribute as much as possible to open-source projects (e.g. &lt;a href="https://github.com/nlohmann/json"&gt;JSON for Modern C++&lt;/a&gt;, &lt;a href="https://github.com/fmtlib/fmt/"&gt;fmt&lt;/a&gt;, &lt;a href="https://github.com/rbock/sqlpp11"&gt;sqlpp11&lt;/a&gt;). Relying on external projects comes with the daunting specter of dependency management, which is one of the major challenges in C++.&lt;/p&gt;

&lt;p&gt;This is less true nowadays, thanks to the emergence of C++ package managers (Conan, Hunter and Vcpkg being the most famous). We chose Conan for various reasons, mostly for its flexibility. It has become a central element of our development environment by allowing us to make cross-building a very simple (you can watch my &lt;a href="https://youtu.be/Gm2h8ZWCEH4?t=58"&gt;swampUP 2018&lt;/a&gt; talk about this very subject), and providing a great way to build and package our libraries and SDK for multiple platforms. Using Conan also led us to help library authors making their projects packageable.&lt;/p&gt;

&lt;p&gt;One key point of our architecture design is to allow building our SDK for several platforms, not only just for mobiles. This implied writing bindings in several programming languages. This is why we opted for having a thin C interface, wrapping our C++ API. C is a much simpler language that a lot of other languages can bind to natively. We keep this interface as simple as possible because writing bindings is still cumbersome. This has been described by &lt;a href="https://dev.to/adishavit"&gt;Adi Shavit&lt;/a&gt; as &lt;a href="http://videocortex.io/2017/salami-method/"&gt;The Salami Method&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Thanks to that, we could easily write an iOS binding since Objective-C can directly call C code. Android still required some work but we used &lt;a href="https://github.com/java-native-access/jna"&gt;JNA&lt;/a&gt; to avoid writing JNI code ourselves. If there is a need for a Go, Rust, or C# binding in the future, it will be way easier to bind than if we had only C++.&lt;/p&gt;

&lt;h2&gt;
  
  
  In conclusion
&lt;/h2&gt;

&lt;p&gt;Using various open-source libraries and managing them with Conan greatly helped us to bring our SDK to Linux, macOS, Windows, iOS and Android. Dropbox’s choices may have been justified at the time they built their product; however, nowadays, C++ seems to be a very viable way of sharing code across multiple platforms.&lt;/p&gt;

&lt;p&gt;We plan on writing more articles to cover dependency management and cross-build processes using Conan and CI scripts at Tanker.&lt;/p&gt;




&lt;p&gt;PS: This article was originally written by &lt;a href="https://dev.to/theodelrieu"&gt;Théo Delrieu&lt;/a&gt; and published on &lt;a href="https://medium.com/tanker-blog/reacting-to-dropbox-another-take-on-cross-platform-c-development-32dd703252d1"&gt;Tanker’s Medium&lt;/a&gt;. As you might not be on Medium yourself, we've reproduced it here to give you a chance to see it in your notifications feed.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>conan</category>
      <category>mobile</category>
      <category>crossplatform</category>
    </item>
    <item>
      <title>How safe are your cat pics?</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Fri, 01 Mar 2019 14:36:17 +0000</pubDate>
      <link>https://dev.to/tanker/how-safe-are-your-cat-pics-4bb9</link>
      <guid>https://dev.to/tanker/how-safe-are-your-cat-pics-4bb9</guid>
      <description>&lt;p&gt;The Internet is like the wild west. Black hats are the bandits, white hats are the bounty hunters. Databases are the new banks, and data is the gold of this era. However, banks always had substantial and sturdy security around them, from vaults to guards and fences.&lt;/p&gt;

&lt;p&gt;Internet apps are sometimes lacking in this area, as shown by the increasingly frequent data breaches we’re observing over the last few years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quantifying security
&lt;/h2&gt;

&lt;p&gt;Let’s consider a simple application, SnapCat, which allows users to share their cat pics. SnapCat has one server, one database and a mobile application with a login form. SnapCat’s makers are obviously concerned about security and want to be sure that their users’ cat pics are safe.&lt;/p&gt;

&lt;p&gt;How can SnapCat measure their app’s security levels?&lt;/p&gt;

&lt;p&gt;A way to estimate a software environment’s security level is to measure its attack surface. This is usually done by a security expert, but the basic concepts are both easy enough and important to understand.&lt;/p&gt;

&lt;p&gt;The first step in measuring the attack surface is to list all the attack vectors. An attack vector is a path an attacker could take to steal data from the environment.&lt;/p&gt;

&lt;p&gt;SnapCat listed a few attack vectors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Getting direct access to the database, by guessing admin credentials, or by using an SQL injection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tricking the server into giving access to data by exploiting an API bug&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attacking the application directly by infecting the libraries it uses &lt;a href="https://arstechnica.com/information-technology/2016/03/rage-quit-coder-unpublished-17-lines-of-javascript-and-broke-the-internet/"&gt;like what happened here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Having SnapCat’s offices infiltrated by an undercover agent working for their #1 competitor, DogPix&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While obviously simplified, these attack vectors are plausible examples of what one could for a cloud-based app. A real-life attack vector analysis could list hundreds of them.&lt;/p&gt;

&lt;p&gt;The next step is, for each attack vector, to evaluate the impact of a successful attack.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Getting access to the database (by direct access or SQL injection) has the most potential impact, as the attacker would get access to all the world’s cat pics in one go.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exploiting an API bug has less impact, as it would require more time to extract the same amount of data. Infecting the app’s libraries would have a similar impact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly, an office infiltration from DogPix would have little impact as the data is not stored in the offices. Foolish dogs!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NgmOBaTR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2A1nWcSPCEu9MHHFWP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NgmOBaTR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2A1nWcSPCEu9MHHFWP.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the attack surface
&lt;/h2&gt;

&lt;p&gt;Next, we want to evaluate the difficulty (or rather the relative easiness) of each attack. This is done by taking into account the existing checks and counter-measures for each attack vector.&lt;/p&gt;

&lt;p&gt;To do so, SnapCat’s security expert analyzed every attack vector. Here are the results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Their database uses default credentials. This makes getting access to it very easy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SQL injections and API bugs are more difficult to exploit, and the current development practices as SnapCat ensure a pretty good defense against these attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Infecting dependencies to target a specific application is very hard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, infiltrating SnapCat’s offices would be very difficult for an undercover dog.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this work done, we can graph SnapCat’s attack surface:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1FKaXeMt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AnNaYwxKCebuNDIvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1FKaXeMt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AnNaYwxKCebuNDIvx.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing attack surface
&lt;/h2&gt;

&lt;p&gt;SnapCat now has a clear view of the risks their application incurs and their respective impacts. It is time for action. The goal is to reduce the attack surface as much as possible.&lt;/p&gt;

&lt;p&gt;There are two ways to reduce the attack surface for each attack vector: either make the attack more difficult or reduce its impact.&lt;/p&gt;

&lt;p&gt;Most of the time, making attacks more difficult is the result of following &lt;a href="https://medium.com/tanker-blog/10-best-practices-to-protect-your-users-data-f8fb64d46f09"&gt;good security practices &lt;/a&gt;. Reducing the impact of attacks can be done by reducing the amount of data stored, storing parts of the data in separate locations or encrypting it.&lt;/p&gt;

&lt;p&gt;The first step for SnapCat is to change the database credentials and implement a better password policy to make guessing important credentials more difficult.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--txHJKC06--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AzEJFuxpOhlH9kDDx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--txHJKC06--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AzEJFuxpOhlH9kDDx.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, this already reduces the “database access” attack surface quite a bit, but to drastically reduce the impact of every possible attack, SnapCat chooses to encrypt all cat pictures on their users’ devices, before they even reach the server. To do that, they start using &lt;a href="https://tanker.io"&gt;Tanker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This ensures the strongest data protection possible, making any database or server attack pretty much useless. Every single cat picture is independently encrypted with its own key, and only the sender and the recipient can decrypt it.&lt;/p&gt;

&lt;p&gt;As a result, SnapCat’s updated attack surface now looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--na5ZgKLk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AKNclE135dH21xOy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--na5ZgKLk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2AKNclE135dH21xOy3.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SnapCat’s team can now focus all of their energy on improving their awesome product. And maybe have a nap or two. 📦&lt;/p&gt;

&lt;p&gt;Learn how &lt;strong&gt;you&lt;/strong&gt; can integrate Tanker into your application at &lt;a href="https://www.tanker.io"&gt;https://www.tanker.io&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;PS: This article was originally written by &lt;a href="http://alois-jobard.fr/"&gt;Aloïs Jobard&lt;/a&gt; and published on &lt;a href="https://medium.com/tanker-blog/how-safe-are-your-cat-pics-434317652e4b"&gt;Tanker’s Medium&lt;/a&gt;. As you might not be on Medium yourself, we've reproduced it here to give you a chance to see it in your notifications feed.&lt;/p&gt;

</description>
      <category>cats</category>
      <category>security</category>
      <category>privacy</category>
      <category>javascript</category>
    </item>
    <item>
      <title>tsrc — Handling multiple git repositories without losing your HEAD</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Fri, 08 Feb 2019 11:11:46 +0000</pubDate>
      <link>https://dev.to/tanker/tsrc---handling-multiple-git-repositories-at-scale--eg3</link>
      <guid>https://dev.to/tanker/tsrc---handling-multiple-git-repositories-at-scale--eg3</guid>
      <description>&lt;p&gt;Git has become the mainstream source code manager and is the &lt;em&gt;de facto&lt;/em&gt; standard for many projects, both open and closed source.&lt;/p&gt;

&lt;p&gt;However, it does not scale well with huge code bases commonly found in the enterprise world.&lt;/p&gt;

&lt;p&gt;Some companies decide to put everything in one giant repository. By doing so, they often end up patching existing source control software as &lt;a href="https://code.facebook.com/posts/218678814984400/scaling-mercurial-at-facebook/" rel="noopener noreferrer"&gt;Facebook did with Mercurial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another way to do things is to keep using good old git: by splitting the sources across multiple repositories.&lt;/p&gt;

&lt;h1&gt;
  
  
  Managing complexity
&lt;/h1&gt;

&lt;p&gt;In the "multi-repo" case, how do you keep track of all the repositories, and how do you synchronize changes among them?&lt;/p&gt;

&lt;p&gt;One popular method is to elect a 'master' repository and manage the others ones through the use of git submodules. Unfortunately, it is not always possible to implement this master/slaves design. Furthermore, the ergonomics of &lt;code&gt;git submodule&lt;/code&gt; are not always on par.&lt;/p&gt;

&lt;h1&gt;
  
  
  tsrc
&lt;/h1&gt;

&lt;p&gt;Enter &lt;a href="https://github.com/TankerHQ/tsrc" rel="noopener noreferrer"&gt;tsrc&lt;/a&gt;. We use it every day at &lt;a href="https://tanker.io" rel="noopener noreferrer"&gt;Tanker&lt;/a&gt; to manage our many sources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwvd5t83st0ndl0x79w24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwvd5t83st0ndl0x79w24.png" alt="tsrc screenshot"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;At its core lies the concept of a &lt;code&gt;manifest&lt;/code&gt;. It's a simple YAML file that lists repositories by their relative paths in the workspace (&lt;code&gt;src&lt;/code&gt;) and by URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git@gitlab.local:acme/foo&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git@gitlab.local:acme/bar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means all repositories are listed in one place.&lt;/p&gt;

&lt;p&gt;If you have multiple teams, you may want to use groups so that each team can select which subset of the repositories they need. Each group has a name and a list of repositories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;first_group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;second_group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;d&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;e&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is known to work with at least 300 different git repositories :)&lt;/p&gt;

&lt;h1&gt;
  
  
  Usage
&lt;/h1&gt;

&lt;p&gt;To use tsrc, all you have to do is put the manifest itself in a git repository, and then use the following&lt;br&gt;
commands to create a new workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/work
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;work
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tsrc init git@gitlab.local:acme/manifest.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can select one or several groups with the &lt;code&gt;-g/--group&lt;/code&gt; option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sure all the repositories are up to date
&lt;/h2&gt;

&lt;p&gt;Once the workspace is created, you can update all the repositories at once by using &lt;code&gt;tsrc sync&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The manifest itself is updated first.&lt;/li&gt;
&lt;li&gt;If a new repository has been added to the manifest, it is cloned.&lt;/li&gt;
&lt;li&gt;Lastly, the other repositories are updated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that &lt;code&gt;tsrc sync&lt;/code&gt; only updates the repositories if the changes are trivial. tsrc will not touch any repository if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The repository is dirty&lt;/li&gt;
&lt;li&gt;The local branch has diverged from upstream&lt;/li&gt;
&lt;li&gt;There is no remote tracking branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is up to you to solve these issues any way you see fit. This way, there is no risk of data loss or sudden conflicts to appear. tsrc focuses on doing simple tasks right and never tries to do smart things.&lt;/p&gt;

&lt;p&gt;Lastly, so that you know where manual intervention is required, &lt;code&gt;tsrc sync&lt;/code&gt; also&lt;br&gt;
displays a summary of errors at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ tsrc sync
...
* (2/2) spam/eggs
=&amp;gt; Fetching origin
=&amp;gt; Updating branch
fatal: Not possible to fast-forward, aborting.
Error: Failed to synchronize workspace
* spam/eggs: updating branch failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Managing merge requests
&lt;/h1&gt;

&lt;p&gt;As a bonus, tsrc is also able to automate review workflows, whether you are using GitHub or GitLab.&lt;/p&gt;

&lt;p&gt;More info about all of this &lt;a href="https://tankerhq.github.io/tsrc/" rel="noopener noreferrer"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;We hope you'll give tsrc a try and that you'll find it a handy tool for your own projects.&lt;/p&gt;

&lt;p&gt;tsrc development happens &lt;a href="https://github.com/TankerHQ/tsrc" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to contribute, open issues, and/or give us feedback in the comment section.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS: tsrc is made with love by the same talented people working at tanker.io. We provide an open-source privacy solution that's as user-friendly as possible. Sign up and take it for a spin here: &lt;a href="https://tanker.io/" rel="noopener noreferrer"&gt;https://tanker.io/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>showdev</category>
      <category>enterprise</category>
    </item>
    <item>
      <title>10 best practices to protect your users’ data (and why they’re still not sufficient)</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Tue, 05 Feb 2019 10:03:28 +0000</pubDate>
      <link>https://dev.to/tanker/10-best-practices-to-protect-your-users-data-and-why-theyre-still-not-sufficient-45j1</link>
      <guid>https://dev.to/tanker/10-best-practices-to-protect-your-users-data-and-why-theyre-still-not-sufficient-45j1</guid>
      <description>&lt;p&gt;Over the last ten years, data breaches have become both more damaging and frequent. Massive leaks regularly make the headlines and hackers target businesses of every size, in every field. As former FBI Director Robert Mueller said, “&lt;em&gt;There are only two types of companies: those that have been hacked, and those that will be&lt;/em&gt;”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2A0ktc2HDEZvM0QoPz" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F0%2A0ktc2HDEZvM0QoPz" alt="words biggest data breaches"&gt;&lt;/a&gt;&lt;br&gt;Data source: &lt;a href="https://informationisbeautiful.net/visualizations/worlds-biggest-data-breaches-hacks/" rel="noopener noreferrer"&gt;https://informationisbeautiful.net/visualizations/worlds-biggest-data-breaches-hacks/&lt;/a&gt;
  &lt;/p&gt;

&lt;p&gt;If you own any kind of online business, you’re probably collecting user data, which is valuable to both your company and your users. In its most simple form, it can only be an email address and a password, and even such basic data can have a significant impact if leaked or stolen.&lt;/p&gt;

&lt;p&gt;However, hackers don’t stop at passwords: credit card numbers, personal information, email addresses, business data, everything has some value, and the business impacts of such breaches can be catastrophic. To protect your users’ data, and therefore your business, here are the ten security best practices that you should follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make data theft more difficult
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1- Use a firewall and a VPN to protect company data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Perhaps the most obvious of security practices: protect your internal network from external access. Set up a firewall to protect access to your data and a VPN to secure remote access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2- Have an elaborate password policy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Up to 80% of people reuse their passwords, use personal passwords for work or use very poor passwords, with “123456” and “password” still being the most used passwords in 2018&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. Having an elaborate internal password policy is critical in preventing unwanted access.&lt;/p&gt;

&lt;p&gt;Use a password manager&lt;sup id="fnref2"&gt;2&lt;/sup&gt; and enable two-factor authentication&lt;sup id="fnref3"&gt;3&lt;/sup&gt; wherever possible. All passwords should also periodically be changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3- Backup your data regularly to avoid ransomware attacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The latest trend in cyber criminality is to get access to your business data, encrypt it and extort money from your company to get that data back. This is called a ransomware attack&lt;sup id="fnref4"&gt;4&lt;/sup&gt;, and it can easily be mitigated by having frequently updated backups. These backups should be encrypted and stored in protected locations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4- Build a cybersecurity culture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With remote work and BYOD practices becoming more prevalent, more stress than ever is put on employees’ security awareness. It’s crucial to educate all your employees on security risks and issues and have well-documented security policies for them to follow.&lt;/p&gt;

&lt;p&gt;Phishing or social engineering are common methods used by hackers to gain access to data or information. Your employees should know how to detect and react to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure your website or application
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;5- Use relevant development practices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Releasing a bugged website or app containing a security flaw can lead to pretty disastrous attacks: cross-site scripting, SQL injections, account theft&lt;sup id="fnref5"&gt;5&lt;/sup&gt;…&lt;/p&gt;

&lt;p&gt;Using relevant development practices can reduce the risk of having such vulnerabilities. You should make sure all code pushed to production is reviewed and tested. Dependencies should be kept up to date and checked for vulnerabilities. Use tools&lt;sup id="fnref6"&gt;6&lt;/sup&gt; to automatically detect potential vulnerabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6- Perform third-party security audits&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While it remains necessary to check and test your app’s code yourself, you should not only rely on your team to ensure your app is secure. Hire an external security company to perform security audits of your code and infrastructure on a regular basis (at least once a year).&lt;/p&gt;

&lt;h2&gt;
  
  
  Limit the impact of unauthorized data access
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;7- Don’t store plain text passwords&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Storing user passwords is not an easy task. Too many companies store passwords as is, or use weak/out of date hashing algorithms. Plain text passwords are gold for any hacker, and would severely hinder your users’ trust.&lt;/p&gt;

&lt;p&gt;Hash any password you store using a secure hash algorithm such as Argon2&lt;sup id="fnref7"&gt;7&lt;/sup&gt;, or use a third party authentication provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8- Manage employees’ permissions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While it might be tempting to grant all access to every employee, it creates a gaping hole in your security. Allowing employees to access sensitive data they don’t necessarily need increases the risk of both insider threats and external hackers.&lt;/p&gt;

&lt;p&gt;Employees should be granted access only to information and resources that are necessary for accomplishing their jobs. This is called the principle of least privilege&lt;sup id="fnref8"&gt;8&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9- Monitor network and actions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If someone is stealing your data, you should be able to detect it. You should monitor your network traffic and set up automatic alerts.&lt;/p&gt;

&lt;p&gt;User action monitoring solutions are designed to record every action taken by your employees and immediately detect and investigate suspicious user activity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10- Use at-rest encryption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any data you collect should be stored encrypted. Any cloud storage provider should have an option to automatically encrypt all data. Check that it’s turned on. If you have your own database, use a Key Management Service&lt;sup id="fnref9"&gt;9&lt;/sup&gt; to secure all your data.&lt;/p&gt;

&lt;p&gt;This will prevent any hacker who obtained this data from exploiting it without also gaining access to the master key (which should, hopefully, be extremely difficult).&lt;/p&gt;

&lt;h2&gt;
  
  
  Go further
&lt;/h2&gt;

&lt;p&gt;If you have implemented all of the above, congratulations! You are among the best in class on data security and privacy.&lt;/p&gt;

&lt;p&gt;However, all these countermeasures only make breaches and leaks more difficult to pull off. Insider jobs and elaborate hacks are still possible, and an attacker gaining access to the right admin account could still siphon all user data stored in your database.&lt;/p&gt;

&lt;p&gt;All hope is not lost though, as there is a way to guarantee your users’ security and privacy: give them back the ownership of their data. By using end-to-end encryption directly on each user’s device to secure data, you can guarantee that no one can access it except its rightful owner. This is in-app privacy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At Tanker, we’ve spent the last two years creating an open-source privacy solution that integrates into your web or mobile app and secures user data at the source.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can sign up for free and try it here: &lt;a href="https://tanker.io/" rel="noopener noreferrer"&gt;https://tanker.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;PS: This article was originally written by &lt;a href="http://alois-jobard.fr/" rel="noopener noreferrer"&gt;Aloïs Jobard&lt;/a&gt; and published on &lt;a href="https://medium.com/tanker-blog/10-best-practices-to-protect-your-users-data-f8fb64d46f09" rel="noopener noreferrer"&gt;Tanker’s Medium&lt;/a&gt;. As you might not be on Medium yourself, we've reproduced it here to give you a chance to see it in your notifications feed.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://www.teamsid.com/splashdatas-top-100-worst-passwords-of-2018" rel="noopener noreferrer"&gt;https://www.teamsid.com/splashdatas-top-100-worst-passwords-of-2018&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://www.dashlane.com" rel="noopener noreferrer"&gt;https://dashlane.com&lt;/a&gt;, &lt;a href="https://www.lastpass.com" rel="noopener noreferrer"&gt;https://lastpass.com&lt;/a&gt;, &lt;a href="https://1password.com/" rel="noopener noreferrer"&gt;https://1password.com&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Multi-factor_authentication" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Multi-factor_authentication&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/WannaCry_ransomware_attack" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/WannaCry_ransomware_attack&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://www.owasp.org/index.php/Top_10-2017_Top_10" rel="noopener noreferrer"&gt;https://www.owasp.org/index.php/Top_10-2017_Top_10&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;&lt;a href="https://www.sqreen.io/" rel="noopener noreferrer"&gt;https://www.sqreen.io&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Argon2" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Argon2&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege" rel="noopener noreferrer"&gt;https://en.wikipedia.org/wiki/Principle_of_least_privilege&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;&lt;a href="https://cloud.google.com/kms," rel="noopener noreferrer"&gt;https://cloud.google.com/kms,&lt;/a&gt; &lt;a href="https://aws.amazon.com/kms" rel="noopener noreferrer"&gt;https://aws.amazon.com/kms&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>privacy</category>
      <category>encryption</category>
      <category>dataprotection</category>
      <category>security</category>
    </item>
    <item>
      <title>Encrypting your users’ data is no longer optional</title>
      <dc:creator>Dimitri Merejkowsky</dc:creator>
      <pubDate>Tue, 22 Jan 2019 10:41:22 +0000</pubDate>
      <link>https://dev.to/tanker/encrypting-your-users-data-is-no-longer-optional-2f13</link>
      <guid>https://dev.to/tanker/encrypting-your-users-data-is-no-longer-optional-2f13</guid>
      <description>&lt;p&gt;&lt;em&gt;While balancing privacy and innovation is one of today’s most critical issues in technology development, let’s identify data encryption’s opportunities.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most of today’s digital businesses hold data on behalf of their users. These assets are valuable to both your business and your users, which means they are excellent targets for hackers. Holding onto their data has risks that can hardly be ignored. While Bill Gates said in &lt;a href="https://www.gatesnotes.com/About-Bill-Gates/Year-in-Review-2018" rel="noopener noreferrer"&gt;his review of 2018&lt;/a&gt; that one of the most critical areas of technology development in 2019 is the balance between privacy and innovation, it’s certainly the right time to ask yourself “&lt;em&gt;how to protect my users’ data?&lt;/em&gt;”, especially if you work for a tech company or leading a digital project. With your users’ trust comes great responsibility, especially when more frequent data breaches have damaging consequences on companies’ brand and &lt;a href="https://newsroom.cisco.com/press-release-content?type=webcontent&amp;amp;articleId=1907897" rel="noopener noreferrer"&gt;revenues&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The looming data breaches, their damaging effects on users’ trust and companies’ revenues.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The main risk you face while holding data is qualified by the term &lt;em&gt;“data breach”&lt;/em&gt;. It consists of a partial or total leak of the data from your servers, or the cloud, to individuals harboring evil intentions. Over the last years these attacks have become more frequent and many companies, such as &lt;a href="https://en.wikipedia.org/wiki/Yahoo!_data_breaches" rel="noopener noreferrer"&gt;Yahoo&lt;/a&gt;, &lt;a href="https://www.forbes.com/sites/gordonkelly/2014/05/21/ebay-suffers-massive-security-breach-all-users-must-their-change-passwords/#28976a457492" rel="noopener noreferrer"&gt;eBay&lt;/a&gt;, or &lt;a href="https://en.wikipedia.org/wiki/Sony_Pictures_hack" rel="noopener noreferrer"&gt;Sony&lt;/a&gt;, have experienced critical leaks.&lt;/p&gt;

&lt;p&gt;Still, what are the odds of being targeted by such attacks?&lt;/p&gt;

&lt;p&gt;As Cisco’s Chief Security Officer John Stewart said, a data breach is bound to happen: &lt;em&gt;“You’re eventually going to be hit, it is not worth the effort of thinking you won’t be hit.”&lt;/em&gt; and &lt;em&gt;“The bottom line is that unfortunately, no organization is immune to a data breach in this day and age.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s keep in mind that your users’ data is a keepsake that embodies the trust relationship between you and them.&lt;/p&gt;

&lt;p&gt;By breaking this trust, your organization sends a terrible message: you don’t consider your users’ well being, and you leave the door open for a competitor that will. Branding takes a long time to build, but a second to destroy.&lt;/p&gt;

&lt;p&gt;Data breaches’ consequences are disastrous for any business. This can lead to situations where clients seek an alternative to your product, do not subscribe, or even leave.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let’s make it short: losing control of customer’s data means losing customers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And there is more and more data to prove it. On January 2018, &lt;a href="https://www.cisco.com/c/dam/en_us/about/doing_business/trust-center/docs/privacy-maturity-benchmark-study-2018.pdf" rel="noopener noreferrer"&gt;Cisco’s Privacy Maturity Benchmark study&lt;/a&gt; revealed that 65% of organizations reported sales cycle delays due to clients’ data privacy concerns, with an average of 7.8 weeks delay, and a correlation between the organization’s privacy maturity level and the sales delay duration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3012%2F1%2A7KA76iikSa9HPvAGw58jqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3012%2F1%2A7KA76iikSa9HPvAGw58jqw.png" alt="Cisco 2018 Privacy Maturity Benchmark Study."&gt;&lt;/a&gt;&lt;br&gt;Cisco 2018 Privacy Maturity Benchmark Study.
  &lt;/p&gt;

&lt;p&gt;Above branding and revenues issues, legislation has evolved to take privacy into account. With the GDPR’s implementation within the European Union and AB 375 in California, data breaches now have direct applicable legal consequences and financial sanctions. If your organization treats users’ personal data on the EU territory, the &lt;a href="https://gdpr-info.eu/issues/fines-penalties/" rel="noopener noreferrer"&gt;fine framework&lt;/a&gt; can be up to 20M€, or in the case of an undertaking, up to 4 % of your total global turnover of the preceding fiscal year, whichever is higher.&lt;/p&gt;

&lt;p&gt;So now that you’re aware of Data Breaches’ damaging consequences, let’s explore the best users’ data protection options.&lt;/p&gt;

&lt;p&gt;Actually, there are two options: securing the data access or securing the data itself. In 2019, If you want to protect your users’ data, there are two options: securing the data access or securing the data itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Securing the data access has its limits: unavoidable human mistakes.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s review the steps necessary to secure the data access to your application together.&lt;/p&gt;

&lt;p&gt;First, your code has to follow your organization’s security guidelines. It then has to go through a thorough code review and finally be audited by a third party. As long as there are no human mistakes, these processes guarantee you a secure code base.&lt;/p&gt;

&lt;p&gt;Except that your software is most probably relying on a handful of external software dependencies, services, and hardware. Sadly, many of them are not built and configured with the same level of security expectations as your organization. This last statement is especially true for storage services that can easily have unsafe or confusing default configurations. Even the NSA experienced such an accident with &lt;a href="https://www.zdnet.com/article/nsa-leak-inscom-exposes-red-disk-intelligence-system/" rel="noopener noreferrer"&gt;the leak of Red Disk&lt;/a&gt;, over an Amazon S3 public bucket.&lt;/p&gt;

&lt;p&gt;Moreover, even after all these steps, you have to be prepared for emergencies with a fast established process to find and deploy the fixes for security issues.&lt;/p&gt;

&lt;p&gt;Altogether human mistakes are impossible to avoid and relying on the security of multiple components in an application means that errors are more likely to happen as each additional piece of code, dependency or service to secure adds a layer of uncertainty.&lt;/p&gt;

&lt;p&gt;This is where encryption comes up.&lt;/p&gt;

&lt;p&gt;Over the last decades, encryption has become one of the most popular and effective methods to secure end-users’ data privacy.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When it comes to secure the data itself, vote for end-to-end encryption.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Data encryption is defined as data’s transformation into another form, or code. When data is encrypted, only people with access to a secret key or password can read it.&lt;/p&gt;

&lt;p&gt;In other words, encrypting the data ensures only authorized users can read it. The data is stored encrypted, and any leak from the storage only leaks the encrypted data. To get access to the clear data, one needs access to both the encrypted data and its associated encryption keys. In this regard, data encryption is a strong barrier to data breaches.&lt;/p&gt;

&lt;p&gt;Among the multiple solutions to implement data encryption, let’s evaluate the most common ones: encryption at rest and end-to-end encryption.&lt;/p&gt;

&lt;p&gt;In an application, many stakeholders can have access to data, primarily some or all of the employees that have access to the application server and the storage.&lt;/p&gt;

&lt;p&gt;With encryption at rest, your application servers typically encrypt data before sending it to storage (e.g., a database server, or service in the cloud) and decrypt it after retrieving it from storage. This means any data leak occurring at storage level is not an issue anymore (e.g., an improperly secured database or a database backup made publicly available by mistake). However, all your users’ data are still accessible by the application servers themselves, some or all of your employees, and thus are vulnerable to attacks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4000%2F1%2AmIKJw2_aVAEjyyHq1b5J-A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4000%2F1%2AmIKJw2_aVAEjyyHq1b5J-A.png" alt="Example of using encryption at rest"&gt;&lt;/a&gt;&lt;br&gt;To send a confidential medical result to Patient Bob, Dr. Alice is using a fictive telemedicine app that adopts encryption at rest. The issue here is that Patient Bob’s data is still accessible in the app server and KMS.
  &lt;/p&gt;

&lt;p&gt;In the event of an attack targeting your application servers, clear data becomes accessible at once. Popular services like AWS KMS are widely used to implement encryption in this fashion.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;End-to-end encryption neutralizes servers’ breaches, but it’s not very user-friendly.&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the end-to-end encryption mode, the clear data is not accessible by the application’s employees, but only by the appropriate users. The data is transmitted as encrypted through the multiple layers of the application. The application servers and the storage only handle the encrypted data, and only the relevant user(s) can decrypt it. An attack on the application server leaks nothing, so that’s why you have to try attacking users one by one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4000%2F1%2AbDfDZ6UAAvYur_sJAfuGgQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F4000%2F1%2AbDfDZ6UAAvYur_sJAfuGgQ.png" alt="Example of using end-to-end encryption"&gt;&lt;/a&gt;&lt;br&gt;Here, Dr. Alice is using the same fictive telemedicine app, but here the app has embedded end-to-end encryption. Patient Bob’s medical data is only accessible at users’ device, neutralizing apps’ and servers’ attack.
  &lt;/p&gt;

&lt;p&gt;As ideal as it sounds, end-to-end encryption adoption is very slow. The main hurdles are the difficulty in developing and setting up such technology, but also the impact on your application’s user workflows.&lt;/p&gt;

&lt;p&gt;For example, common impediments on the user experience of PGP are that users have to manually save and retrieve a key to own several devices or that it is only possible to share to a group of users by encrypting again for every individual in the group.&lt;/p&gt;

&lt;p&gt;Obviously, from a user experience standpoint, these issues are not acceptable, but we’ll develop this point in an upcoming blog post :)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The balance between privacy and user experience is an incredible challenge, and it’s the one we, at Tanker, chose to tackle. We strive to provide an open-source privacy solution as user-friendly as possible. Sign up and try it here: &lt;a href="https://tanker.io/" rel="noopener noreferrer"&gt;https://tanker.io/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;PS: This article was originally written by &lt;a href="https://dev.to/banetl"&gt;Loïc Banet&lt;/a&gt; and published on &lt;a href="https://medium.com/tanker-blog/why-encrypting-your-users-data-is-no-longer-an-option-17e6f2c6781b" rel="noopener noreferrer"&gt;Tanker’s Medium&lt;/a&gt;. As you might not be on Medium yourself, we've reproduced it here to give you a chance to see it in your notifications feed.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>encryption</category>
      <category>dataprotection</category>
      <category>databreaches</category>
    </item>
  </channel>
</rss>
