Andre Borie

Fractional technology leader with over a decade of experience. I write about the intersection of business and technology. Available for consulting from March 2026.

Read this first

Blockchains for non-cryptocurrency applications (still) don’t make sense

Author’s note: originally published in 2019, revised January 2026. Having witnessed the NFT and crypto boom and bust of 2021, these arguments have only grown more relevant.

Why blockchains work for cryptocurrencies

Blockchains work for cryptocurrencies because the asset being traded is the blockchain record itself. When you “own” Bitcoin, what you actually own is a cryptographic entry on a distributed ledger stating that your private key controls a certain amount of Bitcoin. This record is signed by the previous owner, verifiable all the way back to the block that originally minted those coins.

This system is self-sufficient because we’ve collectively agreed that the blockchain record is the thing of value. When you sell cryptocurrency, you sign a new record transferring control to someone else’s key and publish this on the blockchain. The buyer verifies this record and its...

Continue reading →


Wrapping static API keys with IAM roles and beating Shai-Hulud

In view of the recent Shai-Hulud supply chain attack where a malicious package install script exfiltrated the current user’s environment variables and static secrets found in their files, I am describing a solution to eliminate static tokens in favor of dynamic, machine/workload-specific credentials that are time- and scope-limited.

Note that this does not aim to prevent malware attacks per se; an attacker obtaining remote code execution on a privileged node is still able to do damage. However, this technique should reduce the impact of any attack by allowing you to convert any long-lived API tokens into shorter-lived ones without relying on the API vendor’s cooperation.

Avoiding static secrets for cloud API consumption

In a modern cloud environment, your services and developers already do not need to use static tokens to interact with the cloud provider’s services.

You can use your...

Continue reading →


Web cache deception vulnerabilities with Cloudflare and Django

If you’re using Django (or any standards-compliant web application using cookie-based sessions) behind Cloudflare, you need to be aware of a massive limitation in their cache implementation that could lead to caching and leakage of private responses (intended for a specific user presenting their session cookie) to everyone.

Background: the Vary header

The Vary response header is used by an origin server to tell any downstream caches that the response was conditional on one or more request headers, whose names are listed in the Vary header.

Web applications list all headers they consulted when generating the response. In Django for example, a middleware automatically patches the response’s Vary header to append Cookie in there if you access request.session.

Standards-compliant downstream caches may cache this response (conditional on Cache-Control headers obviously), but they must use...

Continue reading →


Strong Customer Authentication for the Wise API in Python

This might be of interest if you are interfacing with the Wise (formerly TransferWise) API from a Python project. Note that you need to have regulatory approval to be able to access 2FA/SCA-protected endpoints - speak to your contact at Wise for more info. But once you get your private key enrolled, this code should work.

import typing
from base64 import b64encode
from datetime import datetime
from enum import StrEnum
from typing import List

import niquests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
from niquests import PreparedRequest, Response

TRANSFERWISE_BASE_URL = "https://api.transferwise.com"


class StatementType(StrEnum):
    COMPACT = "COMPACT"   a single statement line per transaction
    FLAT = "FLAT"   accounting statements where
...

Continue reading →


Social media federation is an answer to the wrong question

When the latest ills of social media are discussed, someone will inevitably bring up the fediverse - the decentralized ecosystem of alternative social networks like Mastodon - as the miracle cure.

Yet even a decade after its release, Mastodon remains largely unknown outside technical circles. The reality is that the fediverse solves few (if any) problems while introducing many new ones - and it starts with fundamental issues most advocates overlook.

The real problems with mainstream platforms

Mainstream social media platforms aren’t evil for evil’s sake. Their noxious behaviors stem from a single root cause: an advertising-based business model that prioritizes engagement at the expense of its users.

This manifests as:

  • Opaque algorithms that prioritize engagement-maximizing content over user preferences
  • Inconsistent moderation that often tolerates offensive content despite user...

Continue reading →


Zero-downtime Django database change strategies

Let’s say we have the following model and we have the current version of the application already running and writing to it:

class LogRecord(models.Model):
    timestamp = models.DateTimeField(auto_now_add=True)
    message = models.TextField()

We’d like to add a severity column to it in a zero-downtime fashion, meaning we will not interrupt the currently-running app (it is running some mysterious and important work that cannot be interrupted cleanly):

class LogRecord(models.Model):
    timestamp = models.DateTimeField(auto_now_add=True)
    message = models.TextField()
    severity = models.IntegerField(default=0)

Now, the sudden presence of the extra column won’t be a problem for the existing instances reading records - the ORM will silently ignore them and your application-level code won’t even be aware of it.

Writing records however, will fail - the database now expects every I...

Continue reading →


Fixing spurious teardown test failures with Django’s LiveServerTestCase

If you are using Django’s LiveServerTestCase to do browser-based testing, you might be running into sporadic database failures in between individual test methods.

The root cause is that the server is bound to the lifetime of the test case (aka class), where as its parent TransactionTestCase class does database changes before & after each individual test method (by calling setUp, tearDown and so on).

The problems occur when you still have leftover traffic hitting the development server (stray request, browser instance that’s taking its time to exit, etc) which will conflict with the database changes being attempted.

The solution is to bind the test server’s lifetime to every test method, ensuring the server is no longer running (and no requests can be processed) before any database changes can be attempted.

Here’s an implementation I wrote to successfully resolve the issue in a past...

Continue reading →


Postgres local development tricks

Improve performance

When running projects locally (for development or running tests), you may be able to speed up database performance by adjusting its data durability settings. This would for example translate in faster setUp()/tearDown() in between tests as well as faster database migrations in Django.

The default config Postgres config favors data safety/reliability, but in a local case the DB shouldn’t have anything we care about, so in that case you can change that. If the DB gets corrupted as a result (power failure, etc) it’s easy enough to drop it and restart from scratch.

Assuming you are running Postgres with the default configuration, it should be possible to change the settings straight from a superuser (postgres user) database shell and then restart the server (under the hood it will edit its postgresql.auto.conf file which is included from its main config file).

Run...

Continue reading →


The “backend” pattern in Django settings

In the context of Django reusable apps, I often need to provide a way for the user to configure a connection (or set of) to some database or third-party service, and optionally be able to substitute the driver/adapter class with a custom one.

In that case I’ve settled on what I call the “backend” pattern (not sure if there is a generally-accepted name for this), similar to what Django already does with database backends.

It allows you to define the connections as follows:

PROCESS_CONTROLLERS = {
    'default': {
        'BACKEND': 'project.backends.TCPIPConnector',
        'OPTIONS': {
            'host': 'process-controller.example.com'
        }
    },
    'backup': {
        'BACKEND': 'project.backends.SerialConnector',
        'OPTIONS': {
            'port': '/dev/ttyUSB0'
        }
    }
}

And then get an instance of the BACKEND, instantiated with all the OPTIONS passed as **...

Continue reading →


StrongSwan IKEv2 iOS road-warrior server config

Note: this is an old post from 2016 and hasn’t been updated nor reviewed since. Nowadays I would recommend using Wireguard which is harder to configure insecurely, or at least use something like OpenWrt which provides a nice GUI to configure IKEv2.

Here’s a really basic Strongswan configuration for a single client, authenticated using a PSK. This has been successfully tested with iOS 10 but should work on any other decent OS. It can be useful to secure traffic from public Wi-Fi or a compromised/evil mobile carrier.

Install

Compile and install Strongswan with swanctl support, as most distros’ packages don’t yet have that feature enabled. [2026 note: this is unlikely to be necessary anymore - packaged versions should support this by now]

Configuration

Save the following as /etc/swanctl/swanctl.conf and adjust it according to your setup. Don’t forget to set a secret PSK.

connections
...

Continue reading →