<?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: François Best</title>
    <description>The latest articles on DEV Community by François Best (@franky47).</description>
    <link>https://dev.to/franky47</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%2Fuser%2Fprofile_image%2F51772%2F7bde707b-5883-4040-b3c4-9e5ead140cfa.png</url>
      <title>DEV Community: François Best</title>
      <link>https://dev.to/franky47</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/franky47"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>François Best</dc:creator>
      <pubDate>Tue, 03 Dec 2024 23:33:15 +0000</pubDate>
      <link>https://dev.to/franky47/-1dl8</link>
      <guid>https://dev.to/franky47/-1dl8</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/logrocket" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1506%2Fe0a84c58-6a79-4f06-9149-87a38b84afa8.png" alt="LogRocket" width="200" height="200"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1268652%2Fab93215a-fbd4-479e-b461-ba0da9cce98c.png" alt="" width="512" height="512"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/logrocket/managing-search-parameters-in-nextjs-with-nuqs-283i" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Managing search parameters in Next.js with nuqs&lt;/h2&gt;
      &lt;h3&gt;Megan Lee for LogRocket ・ Dec 2&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#nextjs&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>The Security of GitHub Actions</title>
      <dc:creator>François Best</dc:creator>
      <pubDate>Mon, 24 Feb 2020 14:03:27 +0000</pubDate>
      <link>https://dev.to/franky47/the-security-of-github-actions-ggj</link>
      <guid>https://dev.to/franky47/the-security-of-github-actions-ggj</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally posted on &lt;a href="https://francoisbest.com/posts"&gt;my blog&lt;/a&gt; - &lt;a href="https://francoisbest.com/posts/2020/the-security-of-github-actions"&gt;The Security of GitHub Actions&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt; are a great way to build powerful customised CI/CD workflows using the power of community-driven resources, but they can be tricky to get right in terms of security.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote Code Execution as a Service
&lt;/h2&gt;

&lt;p&gt;What GitHub gave us with Actions is basically the opportunity to run (&lt;a href="https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions#usage-limits"&gt;almost&lt;/a&gt;) any code on their servers. This makes for a large attack surface and lengthy discussions, so let me define some boundaries.&lt;/p&gt;

&lt;p&gt;This article is not about the kind of security regarding attacks against GitHub, but rather against yourself, when implementing a workflow.&lt;/p&gt;

&lt;p&gt;It will also not consider GitHub itself as an adversary, and instead focus on threats coming from compromised third party actions and their impact on our workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attack Vectors
&lt;/h2&gt;

&lt;p&gt;There are a few bad things that can happen to your workflow:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Data Theft
&lt;/h4&gt;

&lt;p&gt;A malicious action leaks/steals your API tokens or other secrets required by legitimate actions.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Data Integrity Breaches
&lt;/h4&gt;

&lt;p&gt;A malicious action modifies one of your built artefacts, injecting it with malicious code or corrupting it before it is processed or deployed by a legitimate action.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Availability
&lt;/h4&gt;

&lt;p&gt;A malicious action crashes on purpose to prevent your workflow from executing successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blessings &amp;amp; Curse of the Community
&lt;/h2&gt;

&lt;p&gt;Having people work on their own actions and contributing them back to the community is definitely a blessing, as can be seen with the flourishing of the JavaScript ecosystem through NPM in the last decade.&lt;/p&gt;

&lt;p&gt;But it comes with its woes, as we have seen in the past. Some famous examples being the &lt;a href="https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm"&gt;&lt;code&gt;leftPad&lt;/code&gt; incident&lt;/a&gt; (an availability &lt;em&gt;"attack"&lt;/em&gt;), the &lt;a href="https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes"&gt;attacks on ESLint&lt;/a&gt; that leaked credentials (data theft) or the &lt;a href="https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident"&gt;&lt;code&gt;event-stream&lt;/code&gt; attack&lt;/a&gt; that targeted Copay's build process (data integrity).&lt;/p&gt;

&lt;p&gt;I guess every popular system will gather the interest of attackers, and in the end the benefits will probably outweigh the risks, as long as some protections are in place. Some are in the hands of GitHub (scanning and removing malicious actions), but some are in the hands of the users.&lt;/p&gt;

&lt;p&gt;So what can you do to protect yourself ?&lt;/p&gt;

&lt;p&gt;There has been some research by &lt;a href="https://julienrenaux.fr/2019/12/20/github-actions-security-risk/"&gt;Julien Renaux&lt;/a&gt; on this topic, where he recommends pinning action versions not by Git tags, but by Git SHA-1, which is immutable.&lt;/p&gt;

&lt;p&gt;This article builds on top of this research, looking specifically at actions using Docker and environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker-based Actions
&lt;/h2&gt;

&lt;p&gt;Actions can run in a Docker container, created from an image pulled from Docker hub or GitHub's Image Registry. You can specify a tag to use for the image, but just like Git tags, &lt;a href="https://renovate.whitesourcesoftware.com/blog/overcoming-dockers-mutable-image-tags/"&gt;Docker tags are not immutable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As an example, I have created a small Node.js image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mhart/alpine-node:slim-12&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; node -e 'console.log("hello")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; franky47/test:foo &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker push franky47/test:foo
foo: digest: sha256:0916addef9806b26b46f685028e8d95d4c37e7ed8e6274b822797e90ae6fd88f size: 740
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; franky47/test:foo
hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later on, I modify the image, rebuild and upload it using the same tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mhart/alpine-node:slim-12&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; node -e 'console.log("evil")'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; franky47/test:foo &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker push franky47/test:foo
foo: digest: sha256:85fe141a80820b9db0631252ca4e06cc3ced6f662c540b9c25da645168ae5be7 size: 740
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; franky47/test:foo
evil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see how the tag transparently allows the evil version to run. The only defence against that is, just like Git, to use the SHA-256 digest hash to pin the image:&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;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; franky47/test@sha256:0916addef9806b26b46f685028e8d95d4c37e7ed8e6274b822797e90ae6fd88f
hello
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; franky47/test@sha256:85fe141a80820b9db0631252ca4e06cc3ced6f662c540b9c25da645168ae5be7
evil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker for Action Authors
&lt;/h2&gt;

&lt;p&gt;Action authors can use Docker too. They add their Dockerfile to the action repository, and tell GitHub where to find it in the &lt;code&gt;action.yml&lt;/code&gt; metadata file. Most of the time, the job runner will build the Docker image from the sources before running it onto the workflow.&lt;/p&gt;

&lt;p&gt;Because those images are built out-of-band before the workflow runs, it's less likely that the Docker build context gets injected with malicious files or environment variables to compromise the built image. However, because the Dockerfile and the rest of the action repository come from Git, SHA-1 pinning is still recommended to be sure of what is being built.&lt;/p&gt;

&lt;h4&gt;
  
  
  Performance vs Security
&lt;/h4&gt;

&lt;p&gt;It seems wasteful to rebuild images for every workflow run that depends on a Docker-based action. The image may take a long time to build, and that time is taken from the usage limits of everyone who depends on your action, it slows their workflows down, and it generally wastes energy.&lt;/p&gt;

&lt;p&gt;Once your action is stable, you can build and publish the Docker image, then pin it to your &lt;code&gt;action.yml&lt;/code&gt; file by digest hash:&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="c1"&gt;# action.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Some action&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker://franky47/test@sha256:0916addef9806b26b46f685028e8d95d4c37e7ed8e6274b822797e90ae6fd88f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, the users of your action will pull the image from the Docker registry instead of building it.&lt;/p&gt;

&lt;p&gt;The threat model for this kind of delivery method now shifts from your action's users to your own workflow (the one you use to build &amp;amp; deploy the Docker image). But it has a few advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can review and pin any action you may need to build the image&lt;/li&gt;
&lt;li&gt;Your image cannot be compromised by being built outside of a boundary you control.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the threat model of the Docker registry being compromised or untrusted is out of the scope of this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Keeping Up With Security Updates
&lt;/h2&gt;

&lt;p&gt;So what about security updates ? If versions are pinned forever, we miss out on critical vulnerabilities being patched up in the actions we use, their dependencies and all the dependency graph.&lt;/p&gt;

&lt;p&gt;Unfortunately for now, while security and maintenance updates of dependencies can be automated for action authors, action users have to manually check and update their actions, and remember to pin the SHA-1 hash every time.&lt;/p&gt;

&lt;p&gt;Services like &lt;a href="https://dependabot.com/github-actions/"&gt;Dependabot&lt;/a&gt; will eventually become able to analyse the dependency tree of a workflow file, make sure with &lt;a href="https://securitylab.github.com/tools/codeql"&gt;CodeQL&lt;/a&gt; that it is free of known vulnerabilities or malicious code, and suggest updates back to the workflow file, hopefully in the form of SHA-1 pinnings.&lt;/p&gt;




&lt;p&gt;Regardless of how you provide your action, there is another threat both action authors and consumers need to be aware of:&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Variables
&lt;/h2&gt;

&lt;p&gt;GitHub Actions can communicate between one another through environment variables, in tandem with the I/O system that GitHub provides. It's considered a feature, but it has obvious security implications: &lt;strong&gt;any environment variable exported by an action will be part of the environment of all subsequent actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This means that any action that uses the environment should consider it potentially hostile. One example is the use of the environment for inputs when coupled with the fact that inputs can be optional.&lt;/p&gt;

&lt;p&gt;Let's say an action defines its manifest as such:&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="c1"&gt;# owner/repo/action.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;An action&lt;/span&gt;
&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When used, one can optionally pass an input for the &lt;code&gt;foo&lt;/code&gt; argument:&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="c1"&gt;# workflow.yml&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owner/repo@deadf00d&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This action does not specify 'foo'&lt;/span&gt;
  &lt;span class="c1"&gt;# Here, foo = undefined&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owner/repo@deadf00d&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This action specifies 'foo=bar'&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
  &lt;span class="c1"&gt;# Here, foo = 'bar'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first call of the &lt;code&gt;owner/repo&lt;/code&gt; action, the &lt;code&gt;INPUT_FOO&lt;/code&gt; environment variable will not be defined, indicating to the action that the user did not specify an input for &lt;code&gt;foo&lt;/code&gt;, asking to use the default value.&lt;/p&gt;

&lt;p&gt;The second call specifies a value, so the action will see &lt;code&gt;process.env.INPUT_FOO === 'bar'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But now if a malicious action inserts itself before those two actions, the first call will be vulnerable to injection:&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="c1"&gt;# Yep, there's even an action to mutate the global environment:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allenevans/set-env@67961d8&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;INPUT_FOO&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;evil&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owner/repo@deadf00d&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This action does not specify 'foo'&lt;/span&gt;
  &lt;span class="c1"&gt;# Here, foo = 'evil'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;owner/repo@deadf00d&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This action specifies 'foo=bar'&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bar&lt;/span&gt;
  &lt;span class="c1"&gt;# Here, foo = 'bar'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call of &lt;code&gt;owner/repo&lt;/code&gt; will think the input &lt;code&gt;foo&lt;/code&gt; was set to &lt;code&gt;evil&lt;/code&gt;, but the second call's explicit definition will take precedence.&lt;/p&gt;

&lt;p&gt;Unfortunately, there seems to be no defence against that kind of behaviour, as there is no way to tell if an input environment variable comes from an explicit definition or from the global environment of the workflow.&lt;/p&gt;

&lt;p&gt;I contacted GitHub on Hacker One regarding that matter, proposing that unspecified inputs be cleared of global values, their response was as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"In an effort to make the CI environment as dynamic as possible,&lt;br&gt;
we've decided to allow full access to the environment and have made the&lt;br&gt;
strict security barrier lie at the ability to write to the workflow file."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Alternative Solution
&lt;/h4&gt;

&lt;p&gt;Because the proposed solution would break existing behaviour, an alternative could be for GitHub to define an additional environment variable that lists the specified inputs, and to make this variable not injectable from the global environment. This way, actions that want to protect themselves could read from this variable, look for suspicious input variables and decide what to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practicality of attacks
&lt;/h2&gt;

&lt;p&gt;GitHub limiting their liability to attacks against the workflow files (a malicious maintainer modifying the workflow file in a sneaky pull request) is fair enough. However I believe there are other vectors in which an action can be compromised, without any GitHub involvement.&lt;/p&gt;

&lt;p&gt;As demonstrated before, there are attacks on the NPM ecosystem, and a lot of actions use JavaScript because of the convenience it brings. A targeted attack on an action's dependency tree could go unnoticed if it activates only in the context of a specific workflow run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Notes
&lt;/h2&gt;

&lt;p&gt;I think there is a market for automated action dependency analysis, where some services like &lt;a href="https://snyk.io/"&gt;Snyk&lt;/a&gt; can analyse workflows and report on vulnerabilities, suggest actions (pun intended) via pull requests and keep a close eye on malicious activity around GitHub Actions.&lt;/p&gt;

</description>
      <category>security</category>
      <category>github</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to store E2EE keys in the browser</title>
      <dc:creator>François Best</dc:creator>
      <pubDate>Fri, 13 Dec 2019 08:20:43 +0000</pubDate>
      <link>https://dev.to/franky47/how-to-store-e2ee-keys-in-the-browser-2550</link>
      <guid>https://dev.to/franky47/how-to-store-e2ee-keys-in-the-browser-2550</guid>
      <description>&lt;p&gt;In end-to-end encrypted apps (E2EE), keys are generated in the client, and never sent to the server in clear-text.&lt;/p&gt;

&lt;p&gt;It all usually starts from credentials provided by the user, such as username &amp;amp; password, which are derived into a strong cryptographic key using key-derivation functions :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FrjLRe6y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/47ng/session-keystore/master/img/key-derivation.png" class="article-body-image-wrapper"&gt;&lt;img alt="Key derivation from master password using PBKDF2" src="https://res.cloudinary.com/practicaldev/image/fetch/s--FrjLRe6y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/47ng/session-keystore/master/img/key-derivation.png" width="846" height="281"&gt;&lt;/a&gt;&lt;br&gt;Key derivation from master password using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#PBKDF2"&gt;PBKDF2&lt;/a&gt;.
  &lt;/p&gt;

&lt;p&gt;But where to store this key ? We can't reasonably ask the user to enter their credentials every time we need the key to encrypt/decrypt something, it would be a terrible UX and it would lead to users picking weaker passwords.&lt;/p&gt;

&lt;p&gt;Let's have a look at what some E2EE apps do, by analyzing the &lt;a href="https://protonmail.com/"&gt;ProtonMail&lt;/a&gt; approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Lifetime
&lt;/h2&gt;

&lt;p&gt;The first thing to define is the lifetime of the key. For browser-based applications, keys usually last as long as the session.&lt;/p&gt;

&lt;p&gt;This rules out &lt;code&gt;localStorage&lt;/code&gt;, &lt;code&gt;Indexed DB&lt;/code&gt; and cookies, but we could use &lt;code&gt;sessionStorage&lt;/code&gt;, or simply keep it in memory only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistence &amp;amp; Page Reloads
&lt;/h2&gt;

&lt;p&gt;Keeping the key in memory has a serious downside: if your user ever reloads the page, the key is gone, and you would have to show a login screen again. Some E2EE apps like &lt;a href="https://bitwarden.com"&gt;Bitwarden&lt;/a&gt; do this for extra security.&lt;/p&gt;

&lt;p&gt;If we want our key to survive page reloads, we need to use some form of storage.&lt;/p&gt;

&lt;p&gt;One thing to know however, is that most browsers will &lt;a href="https://security.stackexchange.com/questions/89937/is-html5-sessionstorage-secure-for-temporarily-storing-a-cryptographic-key"&gt;write&lt;/a&gt; the contents of &lt;code&gt;sessionStorage&lt;/code&gt; to disk when reloading the page.&lt;/p&gt;

&lt;p&gt;This is an issue as we don't want the key to leak, and any write to the filesystem places it outside of our control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Divide to Conquer
&lt;/h2&gt;

&lt;p&gt;The approach taken by ProtonMail is to split the key into two parts, store each part using different techniques, and recompose the key on page load.&lt;/p&gt;

&lt;p&gt;To split the key, it is XORed with a buffer of random bytes. A copy of the original random data is going to be the other part, so that both of them individually are random, but by XORing them together, the randomness cancels out and reveals the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Split
    a = key ^ random
    b = random

# Recompose
    a ^ b
 =&amp;gt; (key ^ random) ^ random
 =&amp;gt; key ^ (random ^ random) 
 =&amp;gt; key ^ 0
 =&amp;gt; key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One part is sent to &lt;code&gt;sessionStorage&lt;/code&gt;, and the other uses a trick discovered by &lt;a href="https://www.thomasfrank.se"&gt;Thomas Frank&lt;/a&gt; named &lt;a href="https://www.thomasfrank.se/sessionvars.html"&gt;SessionVars&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;window.name&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;There is a &lt;code&gt;name&lt;/code&gt; property on the global &lt;code&gt;window&lt;/code&gt; object in the browser. Its value persists across page reloads, but is not written to disk.&lt;/p&gt;

&lt;p&gt;It has been used for &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/name#Notes"&gt;cross-domain communications&lt;/a&gt;, and because other domains can see its value, we can't send anything there in clear text.&lt;/p&gt;

&lt;p&gt;Fortunately, other domains can't access our domain's &lt;code&gt;sessionStorage&lt;/code&gt;, so all they would see in &lt;code&gt;window.name&lt;/code&gt; is random data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Right Amount of Persistence
&lt;/h2&gt;

&lt;p&gt;The key does not need to be saved in those locations at all times however.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;window.name&lt;/code&gt; is writable by everyone, it would be easy for attackers to erase the key if it was stored there as a single source of truth.&lt;/p&gt;

&lt;p&gt;Instead, we can keep the key in memory, and only persist the key to those shared locations when the memory will be destroyed: on page unloads.&lt;/p&gt;

&lt;p&gt;If the user reloaded the page, both parts of the key will be preserved and reassembled on page load, but if they simply closed the tab/window, both parts will be erased by the browser (end of the session).&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;

&lt;p&gt;Now our key has been recomposed, both storage locations can be cleared as we don't want our &lt;a href="https://harrypotter.fandom.com/wiki/Horcrux"&gt;horcruxes&lt;/a&gt; to be left around.&lt;/p&gt;

&lt;p&gt;The original implementation of this system is available in ProtonMail's &lt;a href="https://github.com/ProtonMail/proton-shared/blob/master/lib/helpers/secureSessionStorage.js#L7"&gt;shared library&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing &lt;a href="https://github.com/47ng/session-keystore"&gt;&lt;code&gt;session-keystore&lt;/code&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/47ng/session-keystore"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vcd1VLQa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/47ng/session-keystore/master/img/header%25402x.png" alt="https://github.com/47ng/session-keystore" width="880" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For all to use this key storage technique without depending on ProtonMail's internal library, I built &lt;a href="https://github.com/47ng/session-keystore"&gt;&lt;code&gt;session-keystore&lt;/code&gt;&lt;/a&gt;, a TypeScript implementation with a few extra features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key expiration dates&lt;/li&gt;
&lt;li&gt;Multiple stores&lt;/li&gt;
&lt;li&gt;Key access/modification/expiration callbacks for monitoring&lt;/li&gt;
&lt;li&gt;&lt;em&gt;React hook (coming soon in a separate package)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading !&lt;/p&gt;

&lt;p&gt;Follow me here or on &lt;a href="https://twitter.com/fortysevenfx"&gt;Twitter&lt;/a&gt; for more E2EE &amp;amp; TypeScript content.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
