<?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>Forem: Richard Klose</title>
    <description>The latest articles on Forem by Richard Klose (@richard_klose).</description>
    <link>https://forem.com/richard_klose</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%2F86202%2F0cb8f314-6ae9-4219-be56-ed348995e3b6.jpeg</url>
      <title>Forem: Richard Klose</title>
      <link>https://forem.com/richard_klose</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/richard_klose"/>
    <language>en</language>
    <item>
      <title>🦸‍♂️🔥 How to be a code review superhero</title>
      <dc:creator>Richard Klose</dc:creator>
      <pubDate>Mon, 19 Aug 2019 10:27:20 +0000</pubDate>
      <link>https://forem.com/richard_klose/how-to-be-a-code-review-superhero-56md</link>
      <guid>https://forem.com/richard_klose/how-to-be-a-code-review-superhero-56md</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vOx1JtWN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1531907700752-62799b2a3e84%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D1080%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vOx1JtWN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1531907700752-62799b2a3e84%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D1080%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="🦸‍♂️🔥 How to be a code review superhero"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the last two years, I've put a lot of effort in improving the code review process at my work. While I introduced new code review tools, explained the concepts to my collegues and helped them, when they struggled, I have seen a lot of different pitfalls with code reviews. It wasn't easy to explain everyone the benefits of reviews and to bring them on a good path, where reviews become helpful for each other. I'd like to share some insights on this topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Code review goals
&lt;/h2&gt;

&lt;p&gt;In order to learn, how you can bee good at reviews, you should understand what can be achieved with reviews. The obvious thing might be something like &lt;strong&gt;ensuring high code quality&lt;/strong&gt; , but there is more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By studying their code, you can &lt;strong&gt;learn from your co-developers&lt;/strong&gt; , how they address problems. &lt;/li&gt;
&lt;li&gt;By suggesting improvements, &lt;strong&gt;others can learn from you&lt;/strong&gt; the same way.&lt;/li&gt;
&lt;li&gt;Having a look at all reviews, helps to &lt;strong&gt;keep track of changes in your codebase&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Showing your code to others, helps to &lt;strong&gt;spread knowledge&lt;/strong&gt; about the codebase and technologies used in the project.&lt;/li&gt;
&lt;li&gt;And of course: Reviews help to write &lt;strong&gt;better code&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To achieve this goals, I follow at least these few simple things in every code review:&lt;/p&gt;

&lt;h2&gt;
  
  
  📝 My ultimate code review checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Try to understand every single change
&lt;/h3&gt;

&lt;p&gt;Go through the changes line by line and make sure you know what the change means for the code. If you do not understand whats going on: &lt;strong&gt;Always ask!&lt;/strong&gt; Asking questions, even if they feel dumb, is never bad. Let the author explain the changes, until you understand what's goning on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try to find at least one thing that could have been done better
&lt;/h3&gt;

&lt;p&gt;Even if the review is just one or two simple changes, I try to find space for improvements. This not only helps to improve the code and the project, but also force me to understand the code deeply. But don't search too long, if you can't find any suggestions within a few minutes, continue the review with something else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Always add some positive feedback
&lt;/h3&gt;

&lt;p&gt;Telling people they've done a good job, helps to keep the good mood up in your team. Even if some (or most) of the changes have to be refactored or are useless, you should add some positivity to the review. This helps to ease diffucult situation, espacially if you are reviewing code of someone who tends to get angry fast.&lt;/p&gt;

&lt;p&gt;Also, if you review some one-line-change (or even a on-character-change), adding a 👍 suits most of the time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the scope of the change
&lt;/h3&gt;

&lt;p&gt;Can the change be described with one simple task? Or are several changes packed into one review? The review should always contain only one taks at time. While this can be sometimes difficult to define, it is often easier to determine, if some completely non-relating task have been done together. Also, if the change is really big and spreads across several hundered lines, you should consider, suggesting to split it up into two or more smaller changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try the changes in your environment
&lt;/h3&gt;

&lt;p&gt;This is often optional, but most of the time I try the changes out locally in my environment, even if it passes CI. In our team, everyone has a slightly different environment, which means especially changes at build and environmental scripts can cause problems for someone else. I want to make sure that the changes work for everyone in my team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check for changes that affect the ecosystem around your project
&lt;/h3&gt;

&lt;p&gt;If there are any changes, that might affect other software around your project, you and co-developers should make sure, they are documented and clearly communicated. This is particularly important, if the review contains breaking changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't fight about details
&lt;/h3&gt;

&lt;p&gt;As long as those details don't introduce some new problems, you should not spend too much time and effort in difficult emotional discussion. The importance of positive mood in teams is often underestimated.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤷‍♂️ What else?
&lt;/h2&gt;

&lt;p&gt;For me, there is always one important thumb rule, that is always true and helpful: &lt;strong&gt;Reviews are not about you, they are about the code. Don't take things personally!&lt;/strong&gt; Instead, try to stay calm and take a short break if something happens during a code review, that has the potential for personal conflicts.&lt;/p&gt;

&lt;p&gt;Surely, there is more to look at in a code review. But these few tips helped me in the past to get a positive output from most of the reviews I have done by myself or where I helped others to do a great review. Hopefully they will do it for you too.&lt;/p&gt;

&lt;p&gt;One last thing I'd like to share: If possible, &lt;strong&gt;automate as much as you can&lt;/strong&gt; of your code review process. This will save you a lot of time. The most basic things can be integrated in every CI pipeline. For this I highly recommend to have a look at &lt;a href="https://github.com/caramelomartins/awesome-linters"&gt;code style linters&lt;/a&gt; or tools like &lt;a href="https://danger.systems"&gt;danger&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any additonal and helpful tips for code reviews, I would be very happy to hear them.&lt;/p&gt;

</description>
      <category>review</category>
      <category>communication</category>
      <category>learning</category>
      <category>team</category>
    </item>
    <item>
      <title>Mirroring Releases from GitHub</title>
      <dc:creator>Richard Klose</dc:creator>
      <pubDate>Mon, 17 Jun 2019 07:43:37 +0000</pubDate>
      <link>https://forem.com/richard_klose/mirroring-releases-from-github-4an6</link>
      <guid>https://forem.com/richard_klose/mirroring-releases-from-github-4an6</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally posted on &lt;a href="https://blog.klose.dev/mirroring-releases-from-github/"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--frYSep-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1495722281458-25edbdb08a1c%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D1080%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--frYSep-t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1495722281458-25edbdb08a1c%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D1080%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="Mirroring Releases from GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At any point in time, developers should be able to create an reproducible and identical output from their source code. If your code relies on third party packages, this can be tricky sometimes. One problem is that some packages download precompiled releases from the web, often from GitHub, to speed up and simplify package installation. However, if the repository (or any other source) is removed, builds are not reproducible anymore, they even can't be compiled anymore.&lt;/p&gt;

&lt;p&gt;I'd like to share my approach on how to face this issue. But before I'd like to explain the problem a bit more in detail. If you are not interested in the details, you can safely skip the next paragraph. If you just came here for a working solution, check out my &lt;a href="https://github.com/richardklose/github-release-mirror"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Contents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The problem in detail: How packages download binaries from GitHub&lt;/li&gt;
&lt;li&gt;Building the caching server: Download GitHub Releases to a local server&lt;/li&gt;
&lt;li&gt;Using the caching server: Setup your environment&lt;/li&gt;
&lt;li&gt;Optionally package everything into a docker container&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this post I will use &lt;a href="https://github.com/electron/electron"&gt;electron&lt;/a&gt; and &lt;a href="https://github.com/sass/node-sass"&gt;node-sass&lt;/a&gt; as examples, which can be installed with npm. They are quite popular and are the packages, that caused this issue for me at first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem in detail: How packages download binaries from GitHub&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Compiling node-sass from source takes some time. In most cases it takes even more time than compiling or packaging the project itself that is using it. Because node-sass must be compiled individually for each platform and operating system, its install script can compile it from scratch. Let's have a deeper look in how node-sass is installed.&lt;/p&gt;

&lt;p&gt;When running &lt;code&gt;npm install node-sass&lt;/code&gt;, the install script from package.json is executed. (See &lt;a href="https://docs.npmjs.com/misc/scripts"&gt;https://docs.npmjs.com/misc/scripts&lt;/a&gt; for more info on npm scripts.)&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  ...
  "scripts": {
    "install": "node scripts/install.js",
  },
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;calling the install script from package.json of node-sass&amp;lt;!--kg-card-end: code--&amp;gt;&amp;lt;!--kg-card-begin: code--&amp;gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (process.env.SKIP_SASS_BINARY_DOWNLOAD_FOR_CI) {
  console.log('Skipping downloading binaries on CI builds');
  return;
}
...
if (sass.hasBinary(binaryPath)) {
  console.log('node-sass build', 'Binary found at', binaryPath);
  return;
}
...
if (cachedBinary) {
  console.log('Cached binary found at', cachedBinary);
  ...
  return;
}

download(sass.getBinaryUrl(), binaryPath, function(err) { ... }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the first lines from scripts/install.js that will be executed&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;p&gt;As you can see from the first lines of the install script, three checks are performed, before the actual binary download is started.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Should the download be skipped? If the environment variable &lt;code&gt;SKIP_SASS_BINARY_DOWNLOAD_FOR_CI&lt;/code&gt; is set, the script exits immediately. This will ensure, that nothing is downloaded and the binary is compiled from scratch. (Please keep in mind, that this &lt;a href="https://github.com/sass/node-sass/issues/1453"&gt;might change in the future&lt;/a&gt;.) &lt;/li&gt;
&lt;li&gt;Is there already a binary, at the compile destination? If the scripts finds a binary, it wouldn't need to recompile it.&lt;/li&gt;
&lt;li&gt;Is there already a binary in some caches? I won't go into detail about this caches, but there might already be a binary, which the script could take instead of downloading or compiling it again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all three checks fail, &lt;code&gt;download(sass.getBinaryUrl(), ...)&lt;/code&gt; will be called. This is where things get interesting.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getBinaryUrl() {
  var site = getArgument('--sass-binary-site') ||
             process.env.SASS_BINARY_SITE ||
             process.env.npm_config_sass_binary_site ||
             (pkg.nodeSassConfig &amp;amp;&amp;amp; pkg.nodeSassConfig.binarySite) ||
             'https://github.com/sass/node-sass/releases/download';

  return [site, 'v' + pkg.version, getBinaryName()].join('/');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;getBinaryUrl is the function, that decides, from where the binaries will be downloaded&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getBinaryUrl()&lt;/code&gt; has several sources from where it can take the download URL. They are used in the following order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;npm install --sass-binary-site=&amp;lt;url&amp;gt;&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;The environment variable &lt;code&gt;SASS_BINARY_SITE&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The variable &lt;code&gt;sass_binary_site&lt;/code&gt; from &lt;code&gt;.npmrc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pkg.nodeSassConfig.binarySite&lt;/code&gt;, where &lt;code&gt;pkg&lt;/code&gt; is the content of the &lt;code&gt;package.json&lt;/code&gt; of node-sass&lt;/li&gt;
&lt;li&gt;The default URL: &lt;a href="https://github.com/sass/node-sass/releases/download"&gt;https://github.com/sass/node-sass/releases/download&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, downloading from GitHub is the default way for getting the binary for node-sass, however, you can manually set another download source. This enables us to be no longer dependent on GitHub. We could use any other server instead by changing the download URL, using one of those settings. But before we can do that, we need such a server, that also has all the files, that are available at &lt;a href="https://github.com/sass/node-sass/releases/download"&gt;https://github.com/sass/node-sass/releases/download&lt;/a&gt;. Of course we could download them all manually, but it would be much nicer, if we had a server, that also updates itself automatically with all the files from GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the caching server: Download GitHub Releases to a local server&lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In order to be no longer dependent on GitHub for your own build steps, we can build a local custom server for that. You will need the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An operating system that can run bash scripts. In this post, I'm using Debian, but any other Linux distribution should also work as well as macOS.&lt;/li&gt;
&lt;li&gt;cron, for scheduling the mirror updates.&lt;/li&gt;
&lt;li&gt;curl, for fetching the metadate for releases from the GitHub API.&lt;/li&gt;
&lt;li&gt;jq, for parsing those metadata.&lt;/li&gt;
&lt;li&gt;wget, for downloading the files. (We could also do this with curl, but wget can automatically create subfolders for us)&lt;/li&gt;
&lt;li&gt;nginx, for delivering the downloaded files via http(s). (Of course, any other webserver will also work)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the following steps, I assume that the command are executed as root. If you can't or do not want to use root, you should use sudo, when necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install packages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Starting with a minimal debian system, the required packages must be installed first:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-get install -y cron curl jq wget nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Create a folder and the mirror script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first we need a place, where the downloaded artifacts will be stored. I will be using &lt;code&gt;/mirror&lt;/code&gt; here. Create the directory with:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir /mirror
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Th script will just be a simply bash script. So create a new file at &lt;code&gt;/opt/mirror.sh&lt;/code&gt; and make it executable:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch /opt/mirror.sh
chmod +x /opt/mirror.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Setup the script&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before starting with the implementation of the actual script, a bit of setup should be made, so the script can be used easily:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash

REPO=${1}
MIRROR_DIR="/mirror"
SRC_URL="https://api.github.com/repos/${REPO}/releases"
DEST="${MIRROR_DIR}/${REPO}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some basic variables, that make the script easier to maintaincc&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;code&gt;REPO=${1}&lt;/code&gt; the script can be called with &lt;code&gt;/opt/mirror.sh &amp;lt;repo&amp;gt;&lt;/code&gt;, so we can use it with any GitHub repository, we want. (e.g. &lt;code&gt;/opt/mirror.sh sass/node-sass&lt;/code&gt; or &lt;code&gt;/opt/mirror.sh electron/electron&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If, for some reason, the mirror directory must be changed, using &lt;code&gt;MIRROR_DIR="/mirror"&lt;/code&gt; helps us to do this in one place, so we don't have to change every usage of the directory in the script.&lt;/li&gt;
&lt;li&gt;The GitHub API has an endpoint for the releases of every repository with the repository name in the URL, so we will just do a GET Request against the enpoint in the next step.&lt;/li&gt;
&lt;li&gt;Of course, we want to be able to use this script with multiple repositories, so every repository gets its own subdirectory in &lt;code&gt;/mirror&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Query the GitHub API and parse the data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For querying the data from GitHub, we simply can use &lt;code&gt;curl ${SRC_URL}&lt;/code&gt;. The GitHub API returns JSON Data per default, that looks like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://api.github.com/repos/sass/node-sass/releases

Response:
[
  {
    url: "https://api.github.com/repos/sass/node-sass/releases/16997851",
    html_url: "https://github.com/sass/node-sass/releases/tag/v4.12.0",
    id: 16997851,
    tag_name: "v4.12.0",
    target_commitish: "master",
    name: "v4.12.0",
    draft: false,
    author: { ... },
    created_at: "2019-04-26T10:18:21Z",
    assets: [
      {
        url: "https://api.github.com/repos/sass/node-sass/releases/assets/12251871",
        id: 12251871,
        name: "win32-x64-11_binding.node",
        browser_download_url: "https://github.com/sass/node-sass/releases/download/v4.12.0/win32-x64-11_binding.node",
        ...
      },
      {
        url: "https://api.github.com/repos/sass/node-sass/releases/assets/12251872",
        id: 12251872,
        name: "win32-x64-11_binding.pdb",
        browser_download_url: "https://github.com/sass/node-sass/releases/download/v4.12.0/win32-x64-11_binding.pdb"
      }
    ],
    ...
  },
  ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stripped down response from the GitHub API&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;p&gt;As you can see, GitHub already gives us a lot of information about the releases of node-sass in this example. Basically it's an array of the releases, where every release itself has an array of all the assets of that particular release and their &lt;code&gt;browser_download_url&lt;/code&gt;. That are exactly the files, we want to download and keep in our release cache. Also, every release has a &lt;code&gt;tag_name&lt;/code&gt; which is in most cases the version number of that release, coming from the git tag of that repository. We also should make sure, that we are only mirroring actual releases and no drafts, so we should have a look at the &lt;code&gt;draft&lt;/code&gt; attribute. All the other information about the release date, the author, etc. might be interesting, but is not relevant for us now.&lt;/p&gt;

&lt;p&gt;This means we could strip down, the JSON like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    tag_name: "v4.12.0",
    draft: false,
    assets: [
      browser_download_url: "https://github.com/sass/node-sass/releases/download/v4.12.0/win32-x64-11_binding.node",
      browser_download_url: "https://github.com/sass/node-sass/releases/download/v4.12.0/win32-x64-11_binding.pdb"
    ],
    ...
  },
  ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can easily be done by passing the output to jq:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl ${SRC_URL} | jq '[.[] | {tag_name: .tag_name, draft: .draft, assets: [.assets[].browser_download_url]}]'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Querying the data from GitHub and passing it to jq&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;p&gt;Let me explain the jq filter in detail:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.[] | { ... }&lt;/code&gt;: This means, that alle objects from the array (&lt;code&gt;.[]&lt;/code&gt;) are taken and passed (&lt;code&gt;|&lt;/code&gt;) into a new template.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{tag_name: .tag_name, draft: .draft, assets: [.assets[].browser_download_url]}&lt;/code&gt;: The template has three attributes: &lt;code&gt;tag_name&lt;/code&gt;, &lt;code&gt;draft&lt;/code&gt; and &lt;code&gt;assets&lt;/code&gt;. &lt;code&gt;tag_name&lt;/code&gt; and &lt;code&gt;draft&lt;/code&gt; are filled with the original values from the source data, while &lt;code&gt;assets&lt;/code&gt; is defined as a new array, containing all values of &lt;code&gt;browser_download_url&lt;/code&gt; from every object in &lt;code&gt;assets&lt;/code&gt; of the source data.&lt;/p&gt;

&lt;p&gt;For better handling, I'll save that in a new variable.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RELEASES=$(curl ${SRC_URL} | jq '[.[] | {tag_name: .tag_name, draft: .draft, assets: [.assets[].browser_download_url]}]')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Preparing the download for each release&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, that we have an JSON array, containing only the data we need, we can interate over the objects and prepare the download of the assets, using a for loop:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for RELEASE in $(echo ${RELEASES} | jq -r '.[] | @base64'); do
  DRAFT=$(echo ${RELEASE} | base64 --decode | jq -r '.draft')
  NAME=$(echo ${RELEASE} | base64 --decode | jq -r '.tag_name')
  URLS=$(echo ${RELEASE} | base64 --decode | jq -r '.assets | .[]')
  ...
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this loop, we create three variables out of the JSON data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DRAFT&lt;/code&gt;: contains the value of &lt;code&gt;draft&lt;/code&gt; from the JSON data, which will be &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NAME&lt;/code&gt;: contains the value of &lt;code&gt;tag_name&lt;/code&gt; from the JSON data. We will use this for creating a subfolder for this particular release later. In most cases, this is the version number of the release.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;URLS&lt;/code&gt;: contains all asset URLs as a space separated string. Wget can handle this easily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ruben Koster has written an &lt;a href="https://www.starkandwayne.com/blog/bash-for-loop-over-json-array-using-jq/"&gt;excellent blog post, explaining why we have to base64 encode and decode the JSON in bash loops.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we can start the download, we have to do two checks. At first, we should make sure, that we skip drafts:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [["${DRAFT}" != "false"]]; then
  continue
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, we should check, if the assets already have been downloaded.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RELEASE_DEST=${DEST}/${NAME}
if [[-d ${RELEASE_DEST}]]; then
  continue
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Download all files&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we can easily download all assets from that particular release.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ${RELEASE_DEST}
wget -P ${RELEASE_DEST} --no-verbose ${URLS}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the download is started, we make sure that the destination directory exists. Also, wget is very chatty. By using &lt;code&gt;--no-verbose&lt;/code&gt; we can supress its output so logs are not polluted with unneccessary download progress messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Script completed!&lt;/strong&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash

REPO=${1}
MIRROR_DIR="/mirror"
SRC_URL="https://api.github.com/repos/${REPO}/releases"
DEST="${MIRROR_DIR}/${REPO}"

RELEASES=$(curl ${SRC_URL} | jq '[.[] | {tag_name: .tag_name, draft: .draft, assets: [.assets[].browser_download_url]}]')

for RELEASE in $(echo ${RELEASES} | jq -r '.[] | @base64'); do
  DRAFT=$(echo ${RELEASE} | base64 --decode | jq -r '.draft')
  NAME=$(echo ${RELEASE} | base64 --decode | jq -r '.tag_name')
  URLS=$(echo ${RELEASE} | base64 --decode | jq -r '.assets | .[]')

  if [["${DRAFT}" != "false"]]; then
    continue
  fi

  RELEASE_DEST=${DEST}/${NAME}
  if [[-d ${RELEASE_DEST}]]; then
    continue
  fi

  mkdir -p ${RELEASE_DEST}
  wget -P ${RELEASE_DEST} --no-verbose ${URLS}

done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete mirror script&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Automate the cache update&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With cron, we can run the script regularly, e.g. daily, to update our cache. For every repository, that should be mirrored, a new line must be added to crontab (&lt;code&gt;crontab -e&lt;/code&gt;).&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 4 * * * /opt/mirror.sh sass/node-sass
0 5 * * * /opt/mirror.sh electron/electron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Updating a node-sass and electron cache over night (e.g. at 2:00 AM and 3:00 AM)&amp;lt;!--kg-card-end: code--&amp;gt;&lt;/p&gt;

&lt;p&gt;You should run the script at least once by hand to initialize the cache and to verify that it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8: A simple static webserver for http access to the cache&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The last thing we need is a webserver, that delivers the downloaded files via HTTP. (Hint: Although I'm not covering HTTPS setup in this post, you should consider a HTTPS configuration for you webserver.)&lt;/p&gt;

&lt;p&gt;With nginx, we can use a simple lightweight webserver, with the following configuration placed at &lt;code&gt;/etc/nginx/sites-enabled/mirror.conf&lt;/code&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {
    listen 80 default_server;
    listen [::]:80 default_server;
    root /mirror;

    location / {
        autoindex on;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creating the file, you should reload nginx. (e.g. with systemd in Debian: &lt;code&gt;systemctl reload nginx&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;You can use your favorite browser and browse to the servers IP address, to verify that the webserver works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the caching server: Setup your environment &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Now you can use that mirror in you projects. As you might remember from the beginning of this post, there are several ways e.g. for node-sass to define the download source. I personally prefer using &lt;code&gt;.npmrc&lt;/code&gt; but you can also use the other ways.&lt;/p&gt;

&lt;p&gt;If you do not have a &lt;code&gt;.npmrc&lt;/code&gt; yet, create an empty file with that name and for node-sass and electron add:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sass_binary_site=http://&amp;lt;ip-of-your-server&amp;gt;/sass/node-sass
electron_mirror=http://&amp;lt;ip-of-your-server&amp;gt;/electron/electron/v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both packages now look for folders at that location for the desired version. We have created this folders with our script before (the &lt;code&gt;$RELEASE_DEST&lt;/code&gt;). Note that I've added a &lt;code&gt;v&lt;/code&gt; for electron to the end. This is because the git tags, we used for this subfolder names always start with v (e.g. &lt;code&gt;v2.0.0&lt;/code&gt;), but the electron install script uses the version number without the leading &lt;code&gt;v&lt;/code&gt; when searching for the download.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optionally package everything into a docker container &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;I've published a full working solution of the script in a docker container on &lt;a href="https://github.com/richardklose/github-release-mirror"&gt;GitHub&lt;/a&gt; and &lt;a href="https://cloud.docker.com/repository/registry-1.docker.io/richardklose/github-release-mirror"&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We are using a solution with docker very similar to this at &lt;a href="https://www.auerswald.de/en/"&gt;Auerswald&lt;/a&gt;, but with a few company specific modifications. This way, we can easily maintain and update our mirrors.&lt;/p&gt;

</description>
      <category>github</category>
    </item>
    <item>
      <title>Learn from others mistakes: How not to write a PHP install script </title>
      <dc:creator>Richard Klose</dc:creator>
      <pubDate>Sat, 21 Jul 2018 07:07:58 +0000</pubDate>
      <link>https://forem.com/richard_klose/learn-from-others-mistakes-how-not-to-write-an-php-install-script--516i</link>
      <guid>https://forem.com/richard_klose/learn-from-others-mistakes-how-not-to-write-an-php-install-script--516i</guid>
      <description>&lt;p&gt;I was playing around with different photo library software latetly, mainly because I was looking for something I could selfhost in my home network. I was using Apple Photos and Lightroom before, but this always became a difficult part as soon as I wanted to share my photo library with my wife, including all tags, annotation and metadata.&lt;br&gt;
I stumbled over &lt;a href="http://koken.me"&gt;koken&lt;/a&gt; and wanted to give it a try.&lt;/p&gt;

&lt;p&gt;Koken is not open source but written in PHP, so it is more or less easy to investigate the code and have a look under the hood. Its so called &lt;a href="http://help.koken.me/customer/portal/articles/632102-installation"&gt;"Installer"&lt;/a&gt; is just a PHP script with some HTML and JavaScript, so I started there.&lt;/p&gt;

&lt;p&gt;The last modified date of the "Installer" file is 2017-02-24, which already made me wonder if it's up to date according to security. Having a look inside I discovered it's definitly not.&lt;/p&gt;
&lt;h1&gt;
  
  
  Outdated jQuery (1.8.0)
&lt;/h1&gt;

&lt;p&gt;The first step of the installation process is a server compatibility check. This is triggered by a HTML button click, that executes a JavaScript function called &lt;code&gt;test()&lt;/code&gt;. This function uses jQuerys &lt;code&gt;$.post()&lt;/code&gt; function to trigger the PHP part of the server compatibility check.&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="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;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="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;test&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="nx"&gt;$&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;index.php&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="na"&gt;server_test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;custom_magick_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;magick_path&lt;/span&gt;
    &lt;span class="p"&gt;},&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;data&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;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;The jQuery version that is used here is 1.8.0 which is not only 6 years old by now, but also has &lt;a href="https://www.cvedetails.com/vulnerability-list/vendor_id-6538/product_id-11031/version_id-235566/Jquery-Jquery-1.8.0.html"&gt;security vulnerabilities&lt;/a&gt; known since January 2018. The latest 1.x version of jQuery at 2017-02-24 (the last modified date of the script) was &lt;a href="https://github.com/jquery/jquery/releases/tag/1.12.4"&gt;1.12.4&lt;/a&gt; which at this point already was half a year old. I would expect at least a non-outdated version like 1.12.4, but as this is also known to have security vulnerabilities, better jQuery &amp;gt; 3.0.0 should be used.&lt;/p&gt;

&lt;h1&gt;
  
  
  Script itself must be writeable
&lt;/h1&gt;

&lt;p&gt;On the server side, the &lt;code&gt;server_test&lt;/code&gt; variable from the client is evaluated by PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'database_check'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'server_test'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$current_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$writable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;is_writable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$current_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;is_writable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="mf"&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="nv"&gt;$writable&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$permissions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'fail'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'This directory does not have the necessary permissions to perform the install. Set the permissions on the koken folder and the koken/index.php file to 777.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the first thing that this script checks is whether the current directory and the script file itself is writable by the user that runs the webserver.&lt;br&gt;
Although it totally makes sense that the folder is writable, as koken will be installed here, the script itself should definitly not be writable. Enforcing this means any process that runs with the same user can modify the script at any time. We will see later, that all the download URLs are hardcoded in the script, so they now can easily be modified and pointed to some malicous URL. Of course the script can also be modified to do whatever is possible with PHP.&lt;br&gt;
I assume making the script writable is necessary as it is called &lt;code&gt;index.php&lt;/code&gt; and may later be replaced with koken's real &lt;code&gt;index.php&lt;/code&gt;. A better solution here would be to give it another maybe more descriptive name like &lt;code&gt;install.php&lt;/code&gt; and force the user to delete it manually after the installation. This would not only ensure that the install script can be non-writable but also would help the user to understand a little bit better what's going on here.&lt;/p&gt;
&lt;h1&gt;
  
  
  Weird download
&lt;/h1&gt;

&lt;p&gt;The very next step during the &lt;code&gt;server_test&lt;/code&gt; is a download of &lt;a href="http://www.phpconcept.net/pclzip"&gt;PclZip&lt;/a&gt; (A PHP library for handling zip files).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$download&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$download_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://s3.amazonaws.com/koken-installer/releases/pclzip.lib.txt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pclzip.lib.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's really strange, because the purpose of the &lt;code&gt;server_test&lt;/code&gt; code should be, to test if the server fulfills the requirements. But why is this downloading stuff, before anything truly relevant (e.g. the PHP version) has been tested?&lt;br&gt;
It's also strange, that PclZip is not downloaded from the &lt;a href="http://www.phpconcept.net/pclzip/pclzip-downloads"&gt;official release downloads&lt;/a&gt; but from koken's aws. Comparing koken's version and the official one reveals that it is modified, but the only change is the removal of empty lines and the closing PHP tag (&lt;code&gt;?&amp;gt;&lt;/code&gt;) at the end of the file. &lt;br&gt;
Side note: The version of PclZip they use here (2.8) is also outdated. Thats noteworthy because the following version (2.8.1) includes a fix for PHP 5.3 which is koken's minimum required PHP version, so it totally makes sense to upgrade here.&lt;/p&gt;
&lt;h1&gt;
  
  
  Weird download enables MITM attacks
&lt;/h1&gt;

&lt;p&gt;By looking deeper into the previous seen &lt;code&gt;download&lt;/code&gt; function, I found the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_FOLLOWLOCATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_SSL_VERIFYPEER&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the HTTP Request for the download of PclZip (and probably every other download that will be done later during the installation) follows every HTTP 3xx redirect while not verifying the authenticity of the server that responds. Or in other words: "I'll download whatever I find behind this URL, I don't care who you are, I'll take the file anyway, even if you serve me some malicous stuff". &lt;a href="https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html"&gt;This opens the door for man-in-the-middle-attacks.&lt;/a&gt;&lt;br&gt;
Especially nowadays, where you can &lt;a href="https://letsencrypt.org"&gt;get SSL certificates for free&lt;/a&gt;, there is no need to disable this verification. Just don't do this.&lt;/p&gt;
&lt;h1&gt;
  
  
  Checking browser far too late
&lt;/h1&gt;

&lt;p&gt;This is not a security isssue but more a usability problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;// the previous mentionend download of PclZip&lt;/span&gt;
&lt;span class="nv"&gt;$ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_USER_AGENT'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;// several testing stuff here&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'msie'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'internet explorer'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$browser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'fail'&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'The Koken beta does not currently support Internet Explorer. Please use Chrome, Safari or Firefox.'&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;It's okay to not support Internet Explorer, but this test can already done by a piece of JavaScript in the browser itself before any communication between the client and the server took place. This way, the script has already downloaded stuff onto your server and then enforces you to restart the process, just because you used the wrong browser.&lt;/p&gt;

&lt;h1&gt;
  
  
  More insecure HTTP(S)
&lt;/h1&gt;

&lt;p&gt;Later during the &lt;code&gt;server_test&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$loopback_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;koken_http_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/json'&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 php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;koken_http_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$host_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mf"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$protocol&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'https'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$curl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_SSL_VERIFYHOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$curl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_SSL_VERIFYPEER&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="p"&gt;}&lt;/span&gt;
  &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seriously, people at koken: Please do not disable SSL verifications. &lt;a href="https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html"&gt;This enables man-in-the-middle attacks.&lt;/a&gt; &lt;a href="https://letsencrypt.org"&gt;Get a free SSL certificate&lt;/a&gt; instead and prevent your users from being hacked.&lt;/p&gt;

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

&lt;p&gt;Just by looking into the install script of koken, I got a sneak peak of how the people at koken deal with security. They seem to not really care about it. I found out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They use outdated dependencies with known bugs and security vulnerabilites.&lt;/li&gt;
&lt;li&gt;They force the user to have a PHP script on their server writable by the user that runs PHP, so another PHP script could modify it.&lt;/li&gt;
&lt;li&gt;They already download stuff before they know if koken will run on the server at all.&lt;/li&gt;
&lt;li&gt;They disable the verification of SSL certificates for their downloads during installation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is much more going on in this script, but I'll stop here as these are the things that made me doubt most.&lt;/p&gt;

&lt;p&gt;If this is the way people at koken write software I really cannot recommend using koken. Even if it might be a nice software for photographers, this is such a security flaw for your server. &lt;/p&gt;

&lt;p&gt;I asked them on twitter about this a few days ago and got no response. &lt;br&gt;
&lt;/p&gt;
&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--SP1b-wX1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/843744064369508353/Sy3dw_Y0_normal.jpg" alt="Richard Klose profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Richard Klose
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/richard_klose"&gt;@richard_klose&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      &lt;a href="https://twitter.com/koken"&gt;@koken&lt;/a&gt; Why are you disabling security for https in your production install script?&lt;br&gt;if ($protocol === 'https')&lt;br&gt;{&lt;br&gt;    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);&lt;br&gt;    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);&lt;br&gt;}
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      14:12 PM - 08 Jun 2018
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1005090137158627329" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1005090137158627329" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1005090137158627329" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;br&gt;
&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--SP1b-wX1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/843744064369508353/Sy3dw_Y0_normal.jpg" alt="Richard Klose profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Richard Klose
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/richard_klose"&gt;@richard_klose&lt;/a&gt;

      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      No answer so far. Looks like &lt;a href="https://twitter.com/koken"&gt;@koken&lt;/a&gt; is either dead or they don’t care about security.&lt;br&gt;&lt;a href="https://t.co/NoRLW0TJnb"&gt;twitter.com/richard_klose/…&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      17:40 PM - 13 Jun 2018
    &lt;/div&gt;

      &lt;div class="ltag__twitter-tweet__quote"&gt;
        &lt;div class="ltag__twitter-tweet__quote__header"&gt;
          &lt;span class="ltag__twitter-tweet__quote__header__name"&gt;
            Richard Klose
          &lt;/span&gt;
          &lt;a class="mentioned-user" href="https://dev.to/richard_klose"&gt;@richard_klose&lt;/a&gt;

        &lt;/div&gt;
        @koken Why are you disabling security for https in your production install script?
if ($protocol === 'https')
{
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
}
      &lt;/div&gt;

    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1006954359689760769" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1006954359689760769" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1006954359689760769" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;
&lt;br&gt;
In combination with a last modified date from more than a year ago and also no activity in their blog since a year I have truly no hopes that this situation will change. 

&lt;p&gt;My plea to all of you developers: Please care more about security and don't make your (probably good) software fail by doing without.&lt;/p&gt;

</description>
      <category>php</category>
      <category>vulnerabilities</category>
      <category>security</category>
      <category>https</category>
    </item>
  </channel>
</rss>
