Feed fetched in 100 ms.
Warning Content type is application/rss+xml; charset=utf-8, not text/xml or applicaton/xml.
Feed is 228,778 characters long.
Feed has an ETag of W/"37dae-pzjSKbVy1+5heVdlYID/p4niF+I".
Warning Feed is missing the Last-Modified HTTP header.
Feed is well-formed XML.
Warning Feed has no styling.
This is an RSS feed.
Feed title: Scott Helme
Error Feed self link: https://scotthelme.ghost.io/rss/ does not match feed URL: https://scotthelme.co.uk/rss/.
Feed has an image at https://scotthelme.ghost.io/favicon.png.
Feed has 15 items.
First item published on 2026-01-28T09:36:48.000Z
Last item published on 2025-04-21T11:58:28.000Z
All items have published dates.
Newest item was published on 2026-01-28T09:36:48.000Z.
Home page URL: https://scotthelme.ghost.io/
Error Home page does not have a matching feed discovery link in the <head>.1 feed links in <head>
Home page has a link to the feed in the <body>
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title><![CDATA[Scott Helme]]></title>
<description><![CDATA[Hi, I'm Scott Helme, a Security Researcher, Entrepreneur and International Speaker. I'm the creator of Report URI and Security Headers, and I deliver world renowned training on Hacking and Encryption.]]></description>
<link>https://scotthelme.ghost.io/</link>
<image>
<url>https://scotthelme.ghost.io/favicon.png</url>
<title>Scott Helme</title>
<link>https://scotthelme.ghost.io/</link>
</image>
<generator>Ghost 6.14</generator>
<lastBuildDate>Wed, 28 Jan 2026 09:36:56 GMT</lastBuildDate>
<atom:link href="https://scotthelme.ghost.io/rss/" rel="self" type="application/rss+xml"/>
<ttl>60</ttl>
<item>
<title><![CDATA[Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us]]></title>
<description><![CDATA[<p>Dogfooding is often talked about as a best practice, but I don't often see the results of such activities. For all new features introduced on Report URI, we are always the first to try them out and see how they work. In this post, we'll look</p>]]></description>
<link>https://scotthelme.ghost.io/eating-our-own-dogfood-what-running-report-uri-on-report-uri-taught-us/</link>
<guid isPermaLink="false">69638d1da2de7d0001fa2bb3</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[Content Security Policy]]></category>
<category><![CDATA[Integrity Policy]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Wed, 28 Jan 2026 09:36:48 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us"><p>Dogfooding is often talked about as a best practice, but I don't often see the results of such activities. For all new features introduced on Report URI, we are always the first to try them out and see how they work. In this post, we'll look at a few examples of issues that we found on Report URI using Report URI, and how you can use our platform to identify exactly the same kind of problems!</p><p></p><h4 id="dogfooding">Dogfooding</h4><p>If you're not familiar with the term dogfooding, or 'eating your own dogfood', here's how the Oxford English Dictionary defines it:</p><p></p><blockquote>Computing slang<br>Of a company or its employees: to use a product or service developed by the company, as a means of testing it before it is made available to customers.</blockquote><p></p><p>It's pretty straightforward and something that we've been doing quite literally since the dawn of Report URI over a decade ago. Any new feature that we introduce gets deployed on Report URI first of all, prompting changes, improvements or fixes as required. Once things are going well, we then introduce a small selection of our customers to participate in a closed beta, again to illicit feedback for the same improvement cycle. After that, the feature will go to an open beta, and finally on to general availability. We currently have two features in the final open beta stages of this process, <a href="https://scotthelme.co.uk/capture-javascript-integrity-metadata-using-csp/?ref=scotthelme.ghost.io" rel="noreferrer">CSP Integrity</a> and <a href="https://scotthelme.co.uk/integrity-policy-monitoring-and-enforcing-the-use-of-sri/?ref=scotthelme.ghost.io" rel="noreferrer">Integrity Policy</a>, and it was during the dogfooding stage of these features that some of things we're doing to discuss were found.</p><p></p><h4 id="integrity-policyfinding-scripts-missing-sri-protection">Integrity Policy - finding scripts missing SRI protection</h4><p>If you're not familiar with Subresource Integrity (SRI), you can read my blog post on <a href="https://scotthelme.co.uk/subresource-integrity/?ref=scotthelme.ghost.io" rel="noreferrer">Subresource Integrity: Securing CDN loaded assets</a>, but here's is a quick explainer. When loading a script tag, especially from a 3rd-party, we have no control over the script we get served, and that can lead to some pretty big problems if the script is modified in a malicious way. </p><p>SRI allows you to specify an integrity attribute on a script tag so that the browser can verify the file once it downloads it, protecting against malicious modification. Here's a simple example of a before and after for a script tag without SRI and then with SRI.</p><pre><code>//before
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
//after
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"
integrity="sha256-ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM="
crossorigin="anonymous">
</script></code></pre><p></p><p>There have been literally countless examples where SRI would have saved organisations from costly attacks and data breaches, including that time when <a href="https://scotthelme.co.uk/protect-site-from-cryptojacking-csp-sri/?ref=scotthelme.ghost.io" rel="noreferrer">governments all around the World got hacked</a> because they didn't use it. That said, it can be difficult to enforce the use of SRI and make sure all of your script tags have it, until <a href="https://scotthelme.co.uk/integrity-policy-monitoring-and-enforcing-the-use-of-sri/?ref=scotthelme.ghost.io" rel="noreferrer">Integrity Policy</a> came along. You can now trivially ensure that all scripts across your application are loaded using SRI by adding a simple HTTP Response Header.</p><pre><code>Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(default)</code></pre><p></p><p>This will ask the browser to send a report for any script that is loaded without using SRI, and then, of course, we can go and fix that!</p><p>Here's a couple that we'd missed. First, we had this one come through.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-16.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1742" height="100" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-16.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-16.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2026/01/image-16.png 1600w, https://scotthelme.ghost.io/content/images/2026/01/image-16.png 1742w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This is interesting because it really should have SRI as something in the account section, and it turns out it almost did, but there was a typo!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-15.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1449" height="243" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-15.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-15.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-15.png 1449w" sizes="(min-width: 720px) 720px"></figure><p></p><p>That's an easy fix, and we found another script without SRI a little later too.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-13.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1225" height="375" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-13.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-13.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-13.png 1225w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This script was in our staff admin section and it was missing SRI, it was reported, and we fixed it!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-14.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1447" height="147" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-14.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-14.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-14.png 1447w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Another great thing to consider about this is that you will only receive telemetry reports when there's a problem. If everything is running as it should be on your site, then no telemetry will be sent!</p><p></p><h4 id="content-security-policyfinding-assets-that-shouldnt-be-there">Content Security Policy - finding assets that shouldn't be there</h4><p>The whole point of CSP is to get visibility into what's happening on your site, and that can be what assets are loading, where data is being communicated, and much more. Often, we're looking for indicators of malicious activity, like JavaScript that shouldn't be present, or data being somewhere it shouldn't be. But, sometimes, we can also detect mistakes that were made during development!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-18.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1495" height="163" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-18.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-18.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-18.png 1495w" sizes="(min-width: 720px) 720px"></figure><p></p><p>We started getting reports for these images loading on our site and because they're not from an approved source, they were blocked and reported to us. Looking closely, the images are from our <code>.io</code> domain instead of our <code>.com</code> domain, which is what we use in test/dev environments, but not in production. It seems that someone had inadvertently hardcoded a hostname that they should not have hardcoded and our CSP let us know!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-17.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1452" height="103" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-17.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-17.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-17.png 1452w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Another simple fix for an issue detected quickly and easily using CSP.</p><p></p><h4 id="but-normally-we-dont-find-anything">But normally we don't find anything!</h4><p>Of course, you're only ever going to find a problem by deploying our product if you had a problem to find in the first place. Our goal is always to test these features out and make sure they're ready for our customers, but sometimes, we do happen to find issues in our own site.</p><p>I guess that's really part of the value proposition though, the difference between <em>thinking</em> you don't have a problem and <em>knowing</em> you don't have a problem. Whether or not we'd found anything by deploying these features, we'd have still massively improved our awareness because we could then be confident we didn't have those issues. </p><p>It just so happens that we didn't think we had any problems, but it turns out we did! Do you think you don't have any problems on your site?</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Blink and you'll miss them: 6-day certificates are here!]]></title>
<description><![CDATA[<p>What a great way to start 2026! Let's Encrypt have now made their short-lived certificates <a href="https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability?ref=scotthelme.ghost.io" rel="noreferrer">available</a>, so you can go and start using them right away.</p><p>It wasn't long ago when the <a href="https://scotthelme.co.uk/shorter-certificates-are-coming/?ref=scotthelme.ghost.io" rel="noreferrer">announcement</a> came that by 2029, all certificates will be reduced to a maximum of</p>]]></description>
<link>https://scotthelme.ghost.io/blink-and-youll-miss-them-6-day-certificates-are-here/</link>
<guid isPermaLink="false">694688d8ab4aac00016ee79e</guid>
<category><![CDATA[Let's Encrypt]]></category>
<category><![CDATA[Google Trust Services]]></category>
<category><![CDATA[TLS]]></category>
<category><![CDATA[Short-Lived Certificates]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 19 Jan 2026 10:48:24 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp" alt="Blink and you'll miss them: 6-day certificates are here!"><p>What a great way to start 2026! Let's Encrypt have now made their short-lived certificates <a href="https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability?ref=scotthelme.ghost.io" rel="noreferrer">available</a>, so you can go and start using them right away.</p><p>It wasn't long ago when the <a href="https://scotthelme.co.uk/shorter-certificates-are-coming/?ref=scotthelme.ghost.io" rel="noreferrer">announcement</a> came that by 2029, all certificates will be reduced to a maximum of 47 days validity, and here we are already talking about certificates valid for less than 7 days. Let's Encrypt continue to drive the industry forwards and considerably exceed the reasonable expectations of today.</p><p></p><h4 id="getting-a-short-lived-certificate">Getting a short-lived certificate</h4><p>Of course, what you want to know is how to get one of these certificates! How you do this will change slightly depending on which tool you're using, but you need to specify the <code>shortlived</code> certificate profile when requesting your certificate from Let's Encrypt.</p><p>I'm using <a href="https://acme.sh/?ref=scotthelme.ghost.io" rel="noreferrer">acme.sh</a> and here is the command I used to get one of these certs when I started playing with this last year:</p><pre><code>acme.sh --issue --dns dns_cf -d six-days.scotthelme.co.uk --force --keylength ec-256 --server letsencrypt --cert-profile shortlived</code></pre><p></p><p>After the certificate was issued, I got my notification from Certificate Transparency monitoring via <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>, and I could see the full details of the certificate. Here they are:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image.png" class="kg-image" alt="Blink and you'll miss them: 6-day certificates are here!" loading="lazy" width="870" height="555" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image.png 870w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Just look at that validity period!!</p><p><strong>Valid From</strong>: 15 Nov 2025<br><strong>Valid To</strong>: 22 Nov 2025</p><p>You can find the full details on the <code>shortlived</code> certificate profile from Let's Encrypt, and other supported profiles, on <a href="https://letsencrypt.org/docs/profiles/?ref=scotthelme.ghost.io#shortlived" rel="noreferrer">this page</a>. </p><p></p><h4 id="its-not-just-lets-encrypt">It's not just Let's Encrypt</h4><p>The good news is that Let's Encrypt isn't the only place that you can get your 6-day certificates from either! <a href="https://scotthelme.co.uk/another-free-ca-to-use-via-acme/?ref=scotthelme.co.uk" rel="noreferrer">Google Trust Services</a> also allows you to obtain short-lived certificates, and they have a little more flexibility in that you can request a specific number of days too. Maybe you want 6 days, 12 days, 33 days... Just specify your desired validity period in the request with your ACME client:</p><pre><code>acme.sh --issue --dns dns_cf -d six-days.scotthelme.co.uk --keylength ec-256 --server https://dv.acme-v02.api.pki.goog/directory --extended-key-usage serverAuth --valid-to '+6d'</code></pre><p></p><p>That's another source of 6-day certificates for you, but it did get me wondering.</p><p></p><h4 id="how-low-can-you-go">How low can you go?..</h4><p>Well, fellow certificate nerds, you read my mind!</p><p>The Let's Encrypt <code>shortlived</code> profile doesn't allow for configurable validity periods, none of their profiles do, but GTS does allow for configuration of the validity period... 😎</p><pre><code>acme.sh --issue --dns dns_cf -d one.scotthelme.co.uk --keylength ec-256 --server https://dv.acme-v02.api.pki.goog/directory --extended-key-usage serverAuth --valid-to '+1d'</code></pre><p></p><p>Yes, that command does work, and yes you do get a <strong>ONE-DAY CERTIFICATE</strong>!!</p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-1.png" class="kg-image" alt="Blink and you'll miss them: 6-day certificates are here!" loading="lazy" width="936" height="608" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-1.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-1.png 936w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Just to prove that this really is a thing, here's the PEM encoded certificate!</p><pre><code>-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgIQAzk1h8YknV4TAJJazYXsrjANBgkqhkiG9w0BAQsFADA7
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww
CgYDVQQDEwNXUjEwHhcNMjUxMjE3MjEzNjE1WhcNMjUxMjE4MjIzNjExWjAfMR0w
GwYDVQQDExRvbmUuc2NvdHRoZWxtZS5jby51azBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABN77LaCQqPHQ1Qx4CsEyiEVARRV5WP+qH9ZyLfO9GzJ+tLfDxROHvYPL
YNaCgEiGBbkTOOPOX9qXJPz/g/2AQRejggFaMIIBVjAOBgNVHQ8BAf8EBAMCB4Aw
EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUPnLq
WpoXyBO+jzNGlH1qUr6SYHkwHwYDVR0jBBgwFoAUZmlJ1N4qnJEDz4kOJLgOMANu
iC4wXgYIKwYBBQUHAQEEUjBQMCcGCCsGAQUFBzABhhtodHRwOi8vby5wa2kuZ29v
Zy9zL3dyMS9BemswJQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMS5j
cnQwHwYDVR0RBBgwFoIUb25lLnNjb3R0aGVsbWUuY28udWswEwYDVR0gBAwwCjAI
BgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3Ix
L0FwMDR2SjA1Q3lBLmNybDATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0B
AQsFAAOCAQEAMsMT7AsLtQqzm0FSsDBq33M9/FAz+Su86NQurk8MXXrSjUrdSKhh
zTv2whJcC0W3aPhoqMeeqpsLYQ4AiLgBS2LPoJz2HuFsIfOddrpI3lOHXssT2Wpc
MjofbwEOfkDk+jV/rqbz1q+cjbM2VGfoxILgcA7KxVZX0ylvZf52c2zpA9v+sXKu
pPFKHHDX2UNSfsPODmWLVWdfFk/ZFbr09urei8ZgdsJhRKABD9BW3aV8QP2dMISh
OH6fWcJXrp/w1NjJqIKidMiMgaCe5TDb+j5gOJ+ZLVcKA4WdLtcVYpQIXiT8mIeO
rCYNVSkFN4ZIONedfENemM5GgBqcqbpxMA==
-----END CERTIFICATE-----</code></pre><p></p><h4 id="automation-is-king">Automation is King</h4><p>The great thing about this, and I've been using these certs for weeks now, is that once you're using an ACME client, you're already automated, and once you're automated, the validity period really isn't relevant any more. I'm currently sticking with the 6-day certs, and I will alternate between Let's Encrypt and Google Trust Services, but running these automations more frequently to go from 90 days down to 6 days really doesn't change anything at all, so give it a try!</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[What a Year of Solar and Batteries Really Saved Us in 2025]]></title>
<description><![CDATA[<p>Throughout 2025, I spoke a few times about our home energy solution, including our grid usage, our solar array and our Tesla Powerwall batteries. Now that I have a full year of data, I wanted to take a look at exactly how everything is working out, and, in alignment with</p>]]></description>
<link>https://scotthelme.ghost.io/what-a-year-of-solar-and-batteries-really-saved-us-in-2025/</link>
<guid isPermaLink="false">6962376aa2de7d0001fa2a36</guid>
<category><![CDATA[Tesla Powerwall]]></category>
<category><![CDATA[Solar Power]]></category>
<category><![CDATA[Octopus Energy]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Tue, 13 Jan 2026 11:24:31 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp" alt="What a Year of Solar and Batteries Really Saved Us in 2025"><p>Throughout 2025, I spoke a few times about our home energy solution, including our grid usage, our solar array and our Tesla Powerwall batteries. Now that I have a full year of data, I wanted to take a look at exactly how everything is working out, and, in alignment with our objectives, how much money we've saved!</p><p></p><h4 id="our-setup">Our setup</h4><p>Just to give a quick overview of what we're working with, here are the details on our solar, battery and tariff situation:</p><ul><li>☀️Solar Panels: We have 14x Perlight solar panels managed by Enphase that make up the 4.2kWp array on our roof, and they produce energy when the sun shines, which isn't as often as I'd like in the UK!</li><li>🔋Tesla Powerwalls: We have 3x Tesla Powerwall 2 in our garage that were purchased to help us load-shift our energy usage. Electricity is very expensive in the UK and moving from peak usage which is 05:30 to 23:30 at ~£0.28/kWh, to off-peak usage, which is 23:30 - 05:30 at ~£0.07/kWh, is a significant cost saving.</li><li>💡Smart Tariff: My wife and I both drive electric cars and our electricity provider, Octopus Energy, has a Smart Charging tariff. If we plug in one of our cars, and cheap electricity is available, they will activate the charger and allow us to use the off-peak rate, even at peak times.</li></ul><p></p><p>Now that we have some basic info, let's get into the details!</p><p></p><h4 id="grid-import">Grid Import</h4><p>I have 3 sources of data for our grid import, and all of them align pretty well in terms of their measurements. I have the amount our electricity supplier charged us for, I have my own CT Clamp going via a Shelly EM that feeds in to Home Assistant, and I have the Tesla Gateway which controls all grid import into our home.</p><p>Starting with my Home Assistant data, these are the relevant readings. </p><p>Jan 1st 2025 - 15,106.10 kWh<br>Dec 31st 2025 - 36,680.90 kWh<br>Total: 21,574.80 kWh<br><strong>Total Import: 21.6 MWh</strong></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-3.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1138" height="503" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-3.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-3.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-3.png 1138w" sizes="(min-width: 720px) 720px"></figure><p></p><p>As you can see in the graph, during the summer months we have slightly lower grid usage and the graph line climbs at a lower rate, but overall, we have pretty consistent usage. Looking at what our energy supplier charged, us for, that comes in slightly lower.</p><p><strong>Total Import: 20.1 MWh</strong></p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-8.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="997" height="577" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-8.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-8.png 997w" sizes="(min-width: 720px) 720px"></figure><p></p><p>I'm going to use the figure provided by our energy supplier in my calculations because their equipment is likely more accurate than mine, and also, what they're charging me is the ultimate thing that matters. The final source is our Tesla Gateway, which shows us having imported 21.0 MWh.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-11.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1290" height="1568" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-11.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-11.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-11.png 1290w" sizes="(min-width: 720px) 720px"></figure><p></p><p>It's great to see how all of these sources of data align so poorly! 😅</p><h4 id="grid-export">Grid Export</h4><p>Looking at our export, the graph tells a slightly different story because, as you can see, we didn't really start exporting properly until June, when our export tariff was activated. Prior to June, it simply wasn't worth exporting as we were only getting £0.04/kWh but at the end of May, our export tariff went live and we were then getting paid £0.15/kWh for export. My <a href="https://scotthelme.co.uk/automation-improvements-after-a-tesla-powerwall-outage/?ref=scotthelme.ghost.io" rel="noreferrer">first</a> and <a href="https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">second</a> blog posts cover the full details of this change when it happened if you'd like to read them but for now, just note that it will change the calculations a little later as we only had export for 60% of the year.</p><p><strong>Total Export: 6.0 MWh</strong></p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-9.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="989" height="582" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-9.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-9.png 989w" sizes="(min-width: 720px) 720px"></figure><p></p><p>With our grid export covered the final piece of the puzzle is to look at our solar.</p><p></p><h4 id="solar-production">Solar Production</h4><p>We're really not in the best part of the world for generating solar power, but we've still managed to produce quite a bit of power. Even in the most ideal, perfect scenario, our solar array can only generate 4.2kW of power, and we're definitely never getting near that. Our peak production was 2.841kW on 8th July at 13:00, and you can see our full annual production graph here. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-12.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1582" height="513" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-12.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-12.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-12.png 1582w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Looking at the total energy production for the entire array, you can see it pick up through the sunnier months but remain quite flat during the darker days of the year.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-2.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1132" height="504" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-2.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-2.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-2.png 1132w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Jan 1st 2025 - 2.709 MWh<br>Dec 31st 2025 - 5.874 MWh<br><strong>Solar Production: 3.2 MWh</strong></p><p></p><p>Just to confirm, I also took a look at the Enphase app, which is drawing it's data from the same source to be fair, and it agrees with the 3.2 MWh of generation.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-4.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1157" height="560" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-4.png 1157w" sizes="(min-width: 720px) 720px"></figure><p></p><h4 id="calculating-the-savings">Calculating the savings</h4><p>This isn't exactly straightforward because of the combination of our solar array and excess import/export due to the batteries, but here are the numbers I'm currently working on.</p><p><strong>Total Import: 20.1 MWh<br>Total Export: 6.0 MWh<br>Solar Production: 3.2 MWh</strong></p><p></p><p>That gives us a total household usage of 17.3 MWh.</p><p>(20.1 MWh import + 3.2 MWh solar) − 6.0 MWh export = 17.3 MWh usage</p><p>If we didn't have the solar array providing power, the full 17.3 MWh of consumption would have been chargeable from our provider. If we had only the solar and no battery, assuming a perfect ability to utilise our solar generation, only 14.1 MWh of our usage would need to be imported. The cost of those units of solar generation can be viewed at the peak and off-peak rates as follows.</p><p>Peak rate: 3,200 kWh x £0.28/kWh = £896<br>Off-peak rate: 3,200 kWh x £0.07/kWh = £224</p><p>Given that solar panels only produce during peak electricity rates, it would be reasonable to use the higher price here. A consideration for us though is that we do have batteries, and we're able to load-shift all of our usage into the off-peak rate, so arguably the solar panels only made £224 of electricity. </p><p>The bigger savings come when we start to look at the cost of the grid import. Assuming we had no solar panels, we'd have imported 17.3 MWh of electricity, and with the solar panels and perfect utilisation, we'd have imported 14.1 MWh of electricity. That's quite a lot of electricity and calculating the different costs of peak vs. off-peak by using batteries to load shift our usage gives some quite impressive results.</p><p>Peak rate: 17,300 kWh x £0.28/kWh = £4,844<br>Peak rate with solar: 14,100 kWh x £0.28 = £3,948</p><p>Off-peak rate: 17,300 kWh x £0.07/kWh = £1,211<br>Off-peak rate with solar: 14,100 kWh x £0.07/kWh = £987</p><p>This means there's a potential swing from £4,844 down to £987 with solar and battery, a total potential saving of £3,857!</p><p>This also tracks if we look at our monthly spend on electricity which went from £350-£400 per month down to £50-£100 per month depending on the time of year. But it gets better.</p><p></p><h4 id="exporting-excess-energy">Exporting excess energy</h4><p>Our solar array generates almost nothing in the winter months so our batteries are sized to allow for a full day of usage with basically no solar support. We can go from the start of the peak rate at 05:30 all the way to the off-peak rate at 23:30 without using any grid power. When it comes to the summer months, though, our solar array is producing a lot of power and we clearly have a capability to export a lot more. The batteries can fill up on the off-peak rate overnight at £0.07/kWh, and then export it during the peak rate for £0.15/kWh, meaning any excess solar production or battery capacity can be exported for a reasonable amount.</p><p>If we take a look at the billing information from our energy supplier, we can see that during July, our best month for solar production, we exported a lot of energy. We exported so much energy that it actually fully offset our electricity costs and allowed us to go negative, meaning we were earning money back.</p><p>Here is our electricity import data:</p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-7.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="988" height="623" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-7.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-7.png 988w" sizes="(min-width: 720px) 720px"></figure><p></p><p>And here is our electricity export data:</p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-6.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="983" height="706" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-6.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-6.png 983w" sizes="(min-width: 720px) 720px"></figure><p></p><p>That's a pretty epic scenario, despite us being such high energy consumers, to still have the ability to fully cover our costs and even earn something back! For clarity, we will still have the standing charge component of our bill, which is £0.45/day so about £13.50 per month to go on any given month, but looking at the raw energy costs, it's impressive.</p><p></p><h4 id="the-final-calculation">The final calculation</h4><p>I pulled all of our charges for electricity in 2025 to see just how close my calculations were and to double check everything I was thinking. Earlier, I gave these figures:</p><p>Off-peak rate: 17,300 kWh x £0.07/kWh = £1,211</p><p>If 100% of our electricity usage was at the off-peak rate, we should have paid £1,211 for the year. Adding up all of our monthly charges, our total for the year was £1,608.11 all in, but we need to subtract our standing charge from that.</p><p>Total cost = £1,608.11 - (365 * £0.45)<br><strong>Total import = £1,443.86</strong></p><p></p><p>This means that we got almost all of our usage at the off-peak rate which is an awesome achievement! After the charges for electricity, I then tallied up all of our payments for export.</p><p><strong>Total export = £886.49</strong></p><p></p><p>Another pretty impressive achievement, earning so much in export, which also helps to bring our net electricity cost in 2025 to <strong>£557.37</strong>! To put this another way, the effective rate of our electricity is now just £0.03/kWh.</p><p>£557.37 / 17,300kWh = <strong>£0.03/kWh</strong></p><p></p><h4 id="but-was-it-all-worth-it">But was it all worth it?</h4><p>That's a tricky question to answer, and everyone will have different objectives and desired outcomes, but ours was pretty clear. Running two Electric Vehicles, having two adults working from home full time, me having servers and equipment at home, along with a power hungry hot tub, we were spending too much per month in electricity alone, and our goal was to reduce that.</p><p>Of course, it only makes sense to spend money reducing our costs if we reduce them enough to pay back the investment in the long term, and things are looking good so far. Here are the costs for our installations:</p><p></p><p>£17,580 - Powerwalls #1 and #2 installed.<br>£13,940 - Solar array installed.<br>£7,840 - Powerwall #3 installed.<br>Total cost = £39,360</p><p></p><p>If we assume even a generous 2/3 - 1/3 split between peak and off-peak usage, with no Powerwalls or solar array, our electricity costs for 2025 would have been £3,632.86:</p><p>11,533 kWh x £0.28/kWh = £3,229.24<br>5,766 kWh x £0.07/kWh = £403.62<br>Total = £3,632.86</p><p></p><p>Instead, our costs were only £557.37, meaning we saved £3,078.49 this year. We also only had export capabilities for 7 months of 2025, so in 2026 when we will have 12 months of export capabilities, we should further reduce our costs. I anticipate that in 2026 our electricity costs for the year will be ~£0, and that's our goal.</p><p>Having our full costs returned in ~11 years is definitely something we're happy with, and we've also had protection against several power outages in our area along the way, which is a very nice bonus. Another way to look at this is that the investment is returning ~9%/year.</p><p></p><table>
<thead>
<tr>
<th style="text-align:right">Year</th>
<th style="text-align:right">Cumulative savings (£)</th>
<th style="text-align:right">ROI (%)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:right">1</td>
<td style="text-align:right">3,632.86</td>
<td style="text-align:right">9.23%</td>
</tr>
<tr>
<td style="text-align:right">2</td>
<td style="text-align:right">7,265.72</td>
<td style="text-align:right">18.46%</td>
</tr>
<tr>
<td style="text-align:right">3</td>
<td style="text-align:right">10,898.58</td>
<td style="text-align:right">27.69%</td>
</tr>
<tr>
<td style="text-align:right">4</td>
<td style="text-align:right">14,531.44</td>
<td style="text-align:right">36.92%</td>
</tr>
<tr>
<td style="text-align:right">5</td>
<td style="text-align:right">18,164.30</td>
<td style="text-align:right">46.15%</td>
</tr>
<tr>
<td style="text-align:right">6</td>
<td style="text-align:right">21,797.16</td>
<td style="text-align:right">55.38%</td>
</tr>
<tr>
<td style="text-align:right">7</td>
<td style="text-align:right">25,430.02</td>
<td style="text-align:right">64.61%</td>
</tr>
<tr>
<td style="text-align:right">8</td>
<td style="text-align:right">29,062.88</td>
<td style="text-align:right">73.84%</td>
</tr>
<tr>
<td style="text-align:right">9</td>
<td style="text-align:right">32,695.74</td>
<td style="text-align:right">83.07%</td>
</tr>
<tr>
<td style="text-align:right">10</td>
<td style="text-align:right">36,328.60</td>
<td style="text-align:right">92.30%</td>
</tr>
<tr>
<td style="text-align:right">15</td>
<td style="text-align:right">54,492.90</td>
<td style="text-align:right">138.43%</td>
</tr>
<tr>
<td style="text-align:right">20</td>
<td style="text-align:right">72,657.20</td>
<td style="text-align:right">184.61%</td>
</tr>
<tr>
<td style="text-align:right">25</td>
<td style="text-align:right">90,821.50</td>
<td style="text-align:right">230.76%</td>
</tr>
</tbody>
</table>
<p> </p><p>Of course, at some point during that period, the effective value of the installation will reduce to almost £0, and we have to consider that, but it's doing pretty darn good. If we hadn't needed to add that third Powerwall, this would have been so much better too. We'll see what the future holds, but with the inevitable and continued rise of energy costs, and talk of moving the standing charge on to our unit rate, things might look even better in the future.</p><p></p><h4 id="onwards-to-2026">Onwards to 2026!</h4><p>Now that we have everything properly set up, and I'm happy with all of our Home Assistant automations, we're going to see how 2026 goes. I will definitely circle back in a year from now and see how the numbers played out, and until then, I hope the information here has been useful or interesting 👍</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Report URI Penetration Test 2025]]></title>
<description><![CDATA[<p>Every year, just as we start to put up the Christmas Tree, we have another tradition at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a> which is to conduct our annual penetration test! </p><p>🎅🎄🎁 --> 🩻🔐🥷</p><p>This will be our 6th annual penetration test that we've posted completely publicly,</p>]]></description>
<link>https://scotthelme.ghost.io/report-uri-penetration-test-2025/</link>
<guid isPermaLink="false">693822108d605500017a6622</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[Penetration Test]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 15 Dec 2025 15:36:37 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp" alt="Report URI Penetration Test 2025"><p>Every year, just as we start to put up the Christmas Tree, we have another tradition at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a> which is to conduct our annual penetration test! </p><p>🎅🎄🎁 --> 🩻🔐🥷</p><p>This will be our 6th annual penetration test that we've posted completely publicly, just as before, and we'll be covering a full run down of what was found and what we've done about it. </p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/12/image.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1915" height="356" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/12/image.png 1600w, https://scotthelme.ghost.io/content/images/2025/12/image.png 1915w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="penetration-tests">Penetration Tests</h4><p>If you find this post interesting or would like to see our previous reports, then here are the links to each and every one of those!</p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2020/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2020</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2021/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2021</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2022/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2022</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2023/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2023</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2024/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI Penetration Test 2024</a></p><p></p><h4 id="the-results">The Results</h4><p>2025 has been another good year for us as we've continued to focus on the security of our product, and the results of the test show that. Not only have we added a bunch of new features, we've also made some significant changes to our infrastructure and made countless changes and improvements to existing functionality too. The tester had what was effectively an unlimited scope to target the application, a full 'guidebook' on how to get up and running with our product and a demo call to ensure all required knowledge was handed over before the test. We wanted them to hit the ground running and waste no time getting stuck in.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-1.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="724" height="409" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-1.png 600w, https://scotthelme.ghost.io/content/images/2025/12/image-1.png 724w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Finding an Info rated issue, three Low rated and a Medium severity issue definitely gives us something to talk about, so let's look at that Medium severity first. </p><p></p><h4 id="csv-formula-injection">CSV Formula Injection</h4><p>The entire purpose of our service is to ingest user-generated data and then display that in some way. Every single telemetry report we process comes from either a browser or a mail server, and the entire content, whilst conforming to a certain schema, is essentially free in terms of the values of fields. We have historically focused on, and thus far prevented, XSS from creeping it's way in, but this bug takes a slightly different form. </p><p>Earlier this year, June 11th to be exact, we released a new feature that allowed for a raw export of telemetry data. This was a commonly requested feature from our customers and we provided two export formats for the data, the native JSON that telemetry is ingested in, or a CSV variant too. You can see the export feature being used here on CSP Reports.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-2.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1553" height="543" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-2.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-2.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-2.png 1553w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Because the data export is a raw export, we are providing the telemetry payloads that we received from the browser or email server, just as you would have received them if you collected them yourself. It turns out that as these fields obviously contain user generated data, and you can do some trickery!</p><p>The specific example given by the tester uses a NEL report, but it's not a problem specific to NEL reports, it's possible across all of our telemetry. Looking at the example in the report, you can see how the tester crafted a specific payload:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-3.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1005" height="671" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-3.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-3.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-3.png 1005w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This <code>type</code> value contains an Excel command that will make it through to the CSV export as it represents the raw value sent by the client. The steps to leverage this are pretty convoluted, but I will quickly summarise them here by using an example if you want to target my good friend <a href="https://troyhunt.com/?ref=scotthelme.ghost.io" rel="noreferrer">Troy Hunt</a>, who runs <a href="https://haveibeenpwned.com/?ref=scotthelme.ghost.io" rel="noreferrer">Have I Been Pwned</a> which indeed uses Report URI.</p><ol><li>Identify the subdomain that Troy uses on our service, which is public information. </li><li>Send telemetry events to that endpoint with specifically crafted payloads which will be processed in to Troy's account.</li><li>Troy would then need to view that data in our UI, and for any reason wish to export that data as a CSV file. </li><li>Then, Troy would have to open that CSV file specifically in Excel, and bypass the two security warnings presented when opening the file. </li></ol><p></p><p>There are a couple of points in there that require some very specific actions from Troy, and the chances of all of those things happening are a little far-fetched, but still, we gave it a lot of consideration. The problem I had is that the export is raw, it's meant to be an export of what the browser or email server provided to us so you have a verbatim copy of the raw data. Sanitising that data is also tricky as there isn't a universal way to sanitise CSV because it depends on what application you're going to open it with, something I discovered when testing out our fix!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-4.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1140" height="582" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-4.png 1140w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Our current approach is to add a single quote to the start of the value if we detect that the first non-whitespace character is a potentially dangerous character, and that seems to have reliably solved the issue. I'm happy to hear feedback on this approach, or alternative suggestions, so drop by the comments below if you can contribute!</p><p></p><h4 id="vulnerabilities-in-outdated-dependencies">Vulnerabilities in Outdated Dependencies </h4><p>Not again! Actually, I'm pretty happy with the finding here, but I will explain both of the issues raised and what we did and didn't do about them. </p><h6 id="bootstrap-v3x">Bootstrap v3.x</h6><p>The last version of Bootstrap 3 was v3.4.1 which did have an XSS vulnerability present (<a href="https://security.snyk.io/package/npm/bootstrap/3.4.1?ref=scotthelme.ghost.io" rel="noreferrer">source</a>). We have since cloned v3.4.1 and patched it up to v3.4.4 ourselves to fix various issues that have been found, including the XSS issue raised here. The issue is still flagged in v3.x though, which is why it was flagged in our custom patched version, so in reality, there is no problem here and we're happy to keep using our own version. </p><h6 id="jquery-cookie-v141">jQuery Cookie v1.4.1</h6><p>Another tricky one because the Snyk data that we refer to lists no vulnerability in this library (<a href="https://security.snyk.io/package/npm/jquery.cookie/1.4.1?ref=scotthelme.ghost.io" rel="noreferrer">source</a>) which is why our own tooling hasn't flagged this to us. That said, the NVD does list a CVE for this version of the jQuery Cookie plugin (<a href="https://nvd.nist.gov/vuln/detail/cve-2022-23395?ref=scotthelme.ghost.io" rel="noreferrer">source</a>) but I can't find other data to back that up, including their own link to Snyk which doesn't list a vulnerability. Rather than spend too much time on this for what is a relatively simple plugin, we decided to remove it and implement the functionality that we need ourselves, solving the problem.</p><p></p><p>With both of those issues addressed, I'm happy to say that we can consider this as resolved too.</p><p></p><h4 id="insufficient-session-expiration">Insufficient Session Expiration</h4><p>This issue has been raised previously in our <a href="https://scotthelme.co.uk/report-uri-penetration-test-2021/?ref=scotthelme.co.uk" rel="noreferrer">2021 penetration test</a>, and our position remains similar to what it was back then. Whilst I'm leaning towards 24 hours being on the top end, and we will probably bring this down shortly, I also feel like the recommended 20 minutes is just too short. Going away from your desk for a coffee break and returning to find you've been logged out just seems a little bit too aggressive for us.</p><p>Looking at the other suggested concerns, if you have malware running on your endpoint, or someone gains physical access to extract a session cookie, I feel like you probably have much bigger concerns to address too! Overall, I acknowledge the issue raised and we're currently thinking something in the 12-18 hours range might be better suited. </p><p></p><h4 id="insecure-tls-configuration">Insecure TLS Configuration
</h4><p>You could argue that I know a thing or two about TLS configuration, heck, you can even attend my <a href="https://www.feistyduck.com/training/practical-tls-and-pki?ref=scotthelme.ghost.io" rel="noreferrer">training course</a> on it if you like! The issue that somebody like a pen tester coming in from the outside is that they're always going to lack the specific context on why we made the configuration choices we did, and I also recognise that it's very hard to give generic advice that fits all situations. </p><p>We have priority in our cipher suite list for all of the best cipher suites as some of the first choices, and we have support for the latest protocol versions too. We're doing so well in fact that we get an A grade on the SSL Labs test (<a href="https://www.ssllabs.com/ssltest/analyze.html?d=helios.report-uri.com&ref=scotthelme.ghost.io" rel="noreferrer">results</a>):</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-5.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1284" height="671" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-5.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-5.png 1284w" sizes="(min-width: 720px) 720px"></figure><p></p><p>I'm happy with where our current configuration is but as always, we will keep it in constant review as time goes by!</p><p></p><h4 id="no-account-lockout-or-timeout-mechanism">No Account Lockout or Timeout Mechanism
</h4><p>This is a controversial topic and it's quite interesting to see it come up because we specifically cover this in the <a href="https://www.troyhunt.com/workshops/?ref=scotthelme.co.uk" rel="noreferrer">Hack Yourself First</a> workshop that I deliver alongside Troy Hunt! I absolutely recognise the goal that a mechanism like this would be trying to achieve, but I worry about the potential negative side-effects.</p><p>Using account enumeration it's often trivial to determine if someone has an account on our service, so you might be able to determine that [email protected] is indeed registered. You then may want to start guessing different passwords to try and log in to Troy's account, and there lies the problem. How many times should you be able to sit there and guess a password before something happens? An account lockout mechanism might work along the lines of saying 'after 5 unsuccessful login attempts we will lock the account and require a password reset', or perhaps say 'after 5 unsuccessful login attempts you will not be able to login again for 3 minutes'. Both of these would stop the attacker from making significant progress, but both of them also present an opportunity to be abused by an attacker too. The attacker can now sit and make repeated login attempts to Troy's account and keep it in a perpetually locked state, denying him the use of his account, a Denial-of-Service attack (DoS)! It's for this reason I'm generally not fond of these account lockout / account suspension mechanisms, they do provide an opportunity for abuse. </p><p>Instead, we rely on a different set of protections to try and limit the impact of attacks like these. </p><ol><li>We implement incredibly strict rate-liming on sensitive endpoints, including our authentication endpoints. This would slow an attacker down so the rate at which they could make guesses would be reduced. (This was disabled for the client IP addresses used by the tester)</li><li>We utilise Cloudflare's Bot Management across our application, which includes authentication flows, and if there is any reasonable suspicion that the client is a bot or in some way automated, they would be challenged. This prevents attackers from automating their attacks, ultimately slowing them down. (This was disabled for the client IP addresses used by the tester)</li><li>We have taken exceptional measures around password security. We require strong and complex passwords for our service, check for commonly used passwords against the Pwned Passwords API, use zxcvbn for strength testing, and more. This makes it highly unlikely that the password being guessed could be guessed easily, and you can read the full details on our password security measures <a href="https://scotthelme.co.uk/boosting-account-security-pwned-passwords-and-zxcvbn/?ref=scotthelme.ghost.io" rel="noreferrer">here</a>.</li><li>We support, and have a very high adoption of, 2FA across our service. This means that there's a very good chance that if the attacker was able to guess a password, the next prompt would be to input the TOTP code from the authenticator app!</li></ol><p>Given the above concerns around lockout mechanisms, and the additional measures we have in place, I continue to remain happy with our current position but we will always review these things on an ongoing basis. </p><p></p><h4 id="thats-a-wrap">That's a wrap!</h4><p>Given how much continued development we see in the product, and how much our infrastructure is evolving over time, I'm really pleased to see that our continued efforts to maintain the security of our product, and ultimately our customer's data, has paid off. </p><p>As we look forward to 2026 our "Development Horizon" project board is loaded with cool new features and updates, so be sure to keep an eye out for the exciting new things we have coming!</p><p>If you want to download a copy of our report, the latest report is always available and linked in the <a href="https://report-uri.com/?ref=scotthelme.ghost.io#footer" rel="noreferrer">footer of our site</a>!</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Report URI - outage update]]></title>
<description><![CDATA[<p>This is not a blog post that anybody ever wants to write, but we had some service issues yesterday and now the dust has settled, I wanted to provide an update on what happened. The good news is that the interruption was very minor in the end, and likely went</p>]]></description>
<link>https://scotthelme.ghost.io/report-uri-outage-update/</link>
<guid isPermaLink="false">691dee03d9c78000011bd122</guid>
<category><![CDATA[Report URI]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Wed, 19 Nov 2025 21:24:19 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp" alt="Report URI - outage update"><p>This is not a blog post that anybody ever wants to write, but we had some service issues yesterday and now the dust has settled, I wanted to provide an update on what happened. The good news is that the interruption was very minor in the end, and likely went unnoticed by most of our customers. </p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide-1.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="974" height="141" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide-1.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide-1.png 974w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="what-happened">What happened?</h4><p>I'm sure that many of you are already aware of the issues that Cloudflare experienced yesterday, and their post-mortem is now available <a href="https://blog.cloudflare.com/18-november-2025-outage/?ref=scotthelme.ghost.io" rel="noreferrer">on their blog</a>. It's always tough to have service issues, but as expected Cloudflare handled it well and were transparent throughout. As a customer of Cloudflare that uses many of their services, the Cloudflare outage unfortunately had an impact on our service too. Because of the unique way that our service operates, <strong><em>our subsequent service issues did not have any impact on the websites or operations of our customers</em></strong>. What we do have to recognise, though, is that we may have missed some telemetry events for a short period of time.</p><p></p><h4 id="our-infrastructure">Our infrastructure</h4><p>Because all of the telemetry events sent to us have to pass through Cloudflare first, when Cloudflare were experiencing their service issues, it did prevent telemetry from reaching our servers. If we take a look at the bandwidth for one of our many telemetry ingestion servers, we can clearly see the impact.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/11/ingestion-server.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="991" height="280" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/ingestion-server.png 600w, https://scotthelme.ghost.io/content/images/2025/11/ingestion-server.png 991w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Looking at the graph from the Cloudflare blog showing their 500 error levels, we have a near perfect alignment with us not receiving telemetry during their peak error rates.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/11/image.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="1507" height="733" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/11/image.png 1000w, https://scotthelme.ghost.io/content/images/2025/11/image.png 1507w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">source: Cloudflare</span></figcaption></figure><p></p><p>The good news here, as mentioned above, is that even if a browser can't reach our service and send the telemetry to us, it has no negative impact on our customer's websites, at all, as the browser will simply continue to load the page and try to send the telemetry again later. This is a truly unique scenario where we can have a near total service outage and it's unlikely that a single customer even noticed because we have no negative impact on their application.</p><p></p><h4 id="the-recovery">The recovery</h4><p>Cloudflare worked quickly to bring their service troubles under control and things started to return to normal for us around 14:30 UTC. We could see our ingestion servers start to receive telemetry again, and we started to receive much more than usual. Here's that same view for the server above.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/11/ingestion-server-2.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="997" height="285" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/ingestion-server-2.png 600w, https://scotthelme.ghost.io/content/images/2025/11/ingestion-server-2.png 997w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If we take a look at the aggregate inbound telemetry for our whole service, we were comfortably receiving twice our usual volume of telemetry data.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/11/global-telemetry.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="994" height="282" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/global-telemetry.png 600w, https://scotthelme.ghost.io/content/images/2025/11/global-telemetry.png 994w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This is a good thing and shows that the browsers that had previously tried to dispatch telemetry to us and had failed were now retrying and succeeding. We did keep a close eye on the impact that this level of load was having, and we managed it well, with the load tailing off to our normal levels overnight. Whilst this recovery was really good to see, we have to acknowledge that there will inevitably be telemetry that was dropped during this time, and it's difficult to accurately gauge how much. If the telemetry event was retried successfully by the browser, or the problem also existed either before or after this outage, we will have still processed the event and taken any necessary action. </p><p></p><h4 id="looking-forwards">Looking forwards</h4><p>I've always talked openly about our infrastructure at Report URI, even blogging in detail about the issues we've faced and the changes we've made as a result, much as I am doing here. We depend on several other service providers to build our service, including Cloudflare for CDN/WAF, DigitalOcean for VPS/compute and Microsoft Azure for storage, but sometimes even the big players will have their own problems, just like AWS did recently too. </p><p>Looking back on this incident now, whilst it was a difficult process for us to go through, I believe we're still making the best choices for Report URI and our customers. The likelihood of us being able to build our own service that rivals the benefits that Cloudflare provides is zero, and looking at other service providers to migrate to seems like a knee-jerk overreaction. I'm not looking for service providers that promise to never have issues, I'm looking for service providers that will respond quickly and transparently when they inevitably do have an issue, and Cloudflare have demonstrated that again. It's also this same desire for transparency and honesty that has driven me to write this blog post to inform you that it is likely we missed some of your telemetry events yesterday, and that we continue to consider how we can improve our service further going forwards.</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Integrity Policy - Monitoring and Enforcing the use of SRI]]></title>
<description><![CDATA[<p>This has been a long time coming so I'm excited that we now have a working standard in the browser for monitoring and enforcing the use of SRI across your website assets!</p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png" class="kg-image" alt loading="lazy" width="974" height="141" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png 974w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="sri-refresher">SRI refresher</h4><p>For those that aren't familiar, or would like a quick refresher, here&</p>]]></description>
<link>https://scotthelme.ghost.io/integrity-policy-monitoring-and-enforcing-the-use-of-sri/</link>
<guid isPermaLink="false">691b2097bb6a68000181de36</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[Integrity Policy]]></category>
<category><![CDATA[javascript]]></category>
<category><![CDATA[integrity]]></category>
<category><![CDATA[Integrity Suite]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Wed, 19 Nov 2025 15:01:02 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp" alt="Integrity Policy - Monitoring and Enforcing the use of SRI"><p>This has been a long time coming so I'm excited that we now have a working standard in the browser for monitoring and enforcing the use of SRI across your website assets!</p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png" class="kg-image" alt="Integrity Policy - Monitoring and Enforcing the use of SRI" loading="lazy" width="974" height="141" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png 974w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="sri-refresher">SRI refresher</h4><p>For those that aren't familiar, or would like a quick refresher, here's the TLDR of SRI - Subresource Integrity. When loading assets from a 3rd-party, especially JavaScript, it's a good idea to have some control over what exactly it is you're loading. A typical script tag will allow <em>any</em> script to load in your page, but SRI gives you the ability to make sure the JavaScript you're getting is the JavaScript you wanted... It's all done with the simple addition of an integrity attribute:</p><p></p><pre><code><script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous">
</script></code></pre><p></p><p>That integrity attribute means the browser can now download that file and check the cryptographic fingerprint of the file it downloaded matches the one that you were expecting! If the file is tampered with or modified in some way, the browser can reject it. I have a full blog post about SRI, <a href="https://scotthelme.co.uk/subresource-integrity/?ref=scotthelme.ghost.io" rel="noreferrer">Subresource Integrity: Securing CDN loaded assets</a>, that I wrote <strong><em>more than a decade ago</em></strong> back in 2015!</p><p></p><h4 id="the-rise-of-sri">The rise of SRI</h4><p>It's no surprise that SRI has become a very popular technology. It's free to use, it's an open web standard supported across all browsers, it's unbelievably simple, and it provides invaluable protection against some quite serious threats. Helping to drive that adoption, all of the major CDN providers have been supplying their script tags with SRI attributes for years, it could have stopped some pretty serious attacks in recent history [<a href="https://scotthelme.co.uk/protect-site-from-cryptojacking-csp-sri/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>], and it's now even a recommended strategy in compliance standards like PCI DSS [<a href="https://scotthelme.co.uk/pci-dss-4-0-its-time-to-get-serious-on-magecart/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]. If you have existing assets that don't have SRI protection, and you'd like to add it, you can use free tools like our <a href="https://report-uri.com/home/sri_hash?ref=scotthelme.ghost.io" rel="noreferrer">SRI Hash Generator</a> to take the URL of existing assets and create an SRI compatible script or style tag. But there lies a little bit of a problem. How do you know what assets that you have across your website that are eligible to use SRI, but aren't currently using it? </p><p></p><h4 id="integrity-policy-to-the-rescue">Integrity Policy to the rescue!</h4><p>We now have a way to effortlessly audit all of the dependencies across your application to ensure that they're using SRI. Integrity Policy is an open web standard, requires no code or agent to be deployed, and has no negative impact to speak of. It can be enabled with a single HTTP Response Header:</p><p></p><pre><code>Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(default)</code></pre><p></p><p>As you can see here, I'm using the <code>Report-Only</code> version of the header as it's best to start by gathering information before you consider any enforcing action. I'm setting the policy to monitor JavaScript and I'm instructing it to send telemetry to the <code>default</code> reporting endpoint. If you're not familiar with the <a href="https://scotthelme.co.uk/introducing-the-reporting-api-nel-other-major-changes-to-report-uri/?ref=scotthelme.ghost.io" rel="noreferrer">Reporting API</a> you can read my full blog post, but the summary is that you simply add a HTTP Response Header to let the browser know where to send the telemetry. This endpoint can also be used by a variety of different mechanisms so you can set it once and use it many times.</p><p></p><pre><code>Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://helios.report-uri.com/a/t/g"}],"include_subdomains":true}</code></pre><p></p><p>That's it! With both of those headers set, any time a browser loads one of your pages it will send an event to let you know if there is an asset being loaded without the use of SRI. As the Integrity Policy header was delivered in Report-Only mode, it will still allow the asset to load so there is no negative impact on your site. The idea here would be that you can now go and fix that problem by adding the integrity attribute to the asset, and then the events will no longer be sent.</p><p></p><h4 id="now-in-open-beta">Now in open beta!</h4><p>Integrity Policy is now available on our site and is free to access for all customers during the open beta period. After announcing another brand new feature only a couple of months ago, <a href="https://scotthelme.co.uk/capture-javascript-integrity-metadata-using-csp/?ref=scotthelme.ghost.io" rel="noreferrer">CSP Integrity</a>, these two features will form part of our new Integrity Suite offering that will become generally available in Q1 2026.</p><p>There's no doubt that these new capabilities are a fundamental improvement in client-side security, allowing for a level of native protection in the browser that is simply unmatched. Whilst the details we've covered in the CSP Integrity blog post, and now in this Integrity Policy blog post, are exciting, we still have a <strong><em>lot</em></strong> more to announce in the coming months!</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded]]></title>
<description><![CDATA[<p>We're very public and open about our infrastructure at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>, having written many blog posts about how we process billions of telemetry events every single week. As a result, it's no secret that we use Redis quite heavily across our infrastructure, and some have asked</p>]]></description>
<link>https://scotthelme.ghost.io/cve-2025-49844-the-redis-cvss-10-0-vulnerability-and-how-we-responded/</link>
<guid isPermaLink="false">68e7a6e405c3450001b53f41</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[Redis]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Tue, 14 Oct 2025 08:11:47 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp" alt="CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded"><p>We're very public and open about our infrastructure at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>, having written many blog posts about how we process billions of telemetry events every single week. As a result, it's no secret that we use Redis quite heavily across our infrastructure, and some have asked how the recent security vulnerability discovered in Redis has impacted us.</p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/10/report-uri.png" class="kg-image" alt="CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded" loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/10/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/10/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="tldr">TLDR;</h4><p>The short answer is that <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-49844?ref=scotthelme.ghost.io" rel="noreferrer">CVE-2025-49844</a> hasn't impacted us at all, largely because of our strict stance on security and existing measures that we already had in place. That said, any time something big happens in the industry that might have affected us, we take a look at how we might improve even further.</p><p></p><h4 id="immediate-response">Immediate response</h4><p>This advisory from Redis (<a href="https://github.com/redis/redis/security/advisories/GHSA-4789-qfc9-5f9q?ref=scotthelme.ghost.io" rel="noreferrer">GHSA-4789-qfc9-5f9q</a>) is the first visibility I had into the issue and the specified workaround was to use the ACL feature in Redis to restrict access to the commands that might be abused.</p><p></p><blockquote>An additional workaround to mitigate the problem without patching the redis-server executable is to prevent users from executing Lua scripts. This can be done using ACL to restrict EVAL and EVALSHA commands.</blockquote><p></p><p>This is an easy protection to implement and I know we don't use the <code>EVAL</code> or <code>EVALSHA</code> commands, so we could get this in place immediately and deploy it across the fleet via Ansible.</p><p></p><pre><code># Disable use of the EVAL and EVALSHA commands
redis-cli ACL SETUSER {user} -EVAL -EVALSHA
# Persist the change to config so it survives restarts
redis-cli CONFIG REWRITE</code></pre><p></p><p>With that in place, we can take a little more time to investigate the issue and plan for the upgrade from Redis 8.2.1 to 8.2.2 to properly resolve the problem. </p><p></p><h4 id="upgrading-redis">Upgrading Redis</h4><p>As fate would have it, ~2 months ago I published this blog post: <a href="https://scotthelme.co.uk/were-going-high-availability-with-redis-sentinel/?ref=scotthelme.ghost.io" rel="noreferrer">We're going High Availability with Redis Sentinel!</a> In that blog post, I described our new High Availability setup using Redis Sentinel to give us failover capabilities between our new Primary and Replica caches. I also explained how one of the major benefits of the upgrade would be that updating our Redis server binary becomes a whole lot easier, and here we are!</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/10/image.png" class="kg-image" alt="CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded" loading="lazy" width="562" height="441"><figcaption><span style="white-space: pre-wrap;">Our Redis Sentinels fronting our Primary and Replica caches</span></figcaption></figure><p></p><p>With our new HA setup, it's simply a case of updating and upgrading the Replica cache, promoting it to Primary, and then updating what previously was the Primary cache but is now a Replica. Done!</p><pre><code>redis-cli info
# Server
redis_version:8.2.2
redis_git_sha1:00000000
redis_git_dirty:1
redis_build_id:8ade872ac64c6931
redis_mode:standalone
...</code></pre><p></p><h4 id="existing-protections-and-investigations">Existing protections and investigations</h4><p>Our Redis caches reside on a private network and are not at any time accessible from the public Internet. Further to that, we have firewall rules in place to only allow known IP addresses on our private network to have access to the Redis caches. Just to go the extra distance, we then only allow our application servers with the appropriate roles to have access to the appropriate Redis caches that they require to fulfil that role! Our network access is already locked down as tight as we can get it and there is no anomalous network activity in our logs.</p><p>Looking at the Redis caches themselves, we can use <code>commandstats</code> to get per-command counters on how many times each command has been called against the cache since the server started. To reset the counters it would require access to restart <code>redis-server</code> or call <code>CONFIG RESETSTAT</code>, and the command counters all look aligned with typical volumes before the update. Running the command, we can see no indication of the counts being reset, and there are no instances of <code>EVAL</code> or <code>EVALSHA</code> being called on the servers.</p><p></p><h4 id="further-hardening">Further Hardening</h4><p>With some quick research looking at other powerful commands, I could see that we currently don't require the use of the <code>SCRIPT</code> or <code>FUNCTION</code> commands, so they presented themselves as another good opportunity to further reduce the commands available. </p><p></p><pre><code># Disable use of the SCRIPT and FUNCTION commands
redis-cli ACL SETUSER {user} -SCRIPT -FUNCTION
# Persist the change to config so it survives restarts
redis-cli CONFIG REWRITE</code></pre><p></p><p>There are some other commands that I want to restrict, but they require further investigation to ensure there are no adverse effects. To wrap things up and make sure that all of these restrictions are working as intended, we can try to run some of the commands that should now be blocked.</p><p></p><pre><code>redis-cli EVAL "return 1" 0
(error) NOPERM User default has no permissions to run the 'eval' command
redis-cli ACL LOG 1
1) 1) "count"
2) (integer) 1
3) "reason"
4) "command"
5) "context"
6) "toplevel"
7) "object"
8) "eval"
9) "username"
10) "default"
11) "age-seconds"
12) "54.578"
13) "client-info"
14) "id=49 addr=*snip*:59092 laddr=*snip*:6379 fd=32 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=35 qbuf-free=20439 argv-mem=13 multi-mem=0 rbs=16384 rbp=16384 obl=0 oll=0 omem=0 tot-mem=37797 events=r cmd=eval user=default redir=-1 resp=2 lib-name= lib-ver= io-thread=0 tot-net-in=35 tot-net-out=0 tot-cmds=0"
15) "entry-id"
16) (integer) 0
17) "timestamp-created"
18) (integer) 1760018943783
19) "timestamp-last-updated"
20) (integer) 1760018943783</code></pre><p></p><p>The <code>EVAL</code> command was blocked as expected and the <code>ACL LOG</code> now has an entry to show that the block happened, which makes it easier to track the attempted usage of blocked commands in the future too.</p><p>With the fleet now updated to Redis 8.2.2, the additional protections in place, and no indication that we (or anyone else) were affected, I'm happy to consider this resolved!</p><p></p><h4 id="thats-a-lot-of-data">That's a lot of data!</h4><p>If you're curious just how much data we process, you can see my recent blog post <a href="https://scotthelme.co.uk/trillion-with-a-t-surpassing-2-trillion-events-processed/?ref=scotthelme.ghost.io" rel="noreferrer">Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀</a> which covers the details. That blog post also announced the launch of our public dashboard that provides a live insight into our telemetry volumes, along with heaps of other interesting data, so check that out here too: <a href="https://dash.report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Public Dashboard</a></p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Capture JavaScript Integrity Metadata using CSP!]]></title>
<description><![CDATA[<p>Today we're announcing the open beta of a brand new and incredibly powerful feature on the Report URI platform, CSP Integrity! Having the ability to collect integrity metadata for scripts running on your site opens up a whole new realm of possibilities, and it couldn't be</p>]]></description>
<link>https://scotthelme.ghost.io/capture-javascript-integrity-metadata-using-csp/</link>
<guid isPermaLink="false">68bfe86ad26c1a00012b4a80</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[CSP]]></category>
<category><![CDATA[integrity]]></category>
<category><![CDATA[javascript]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 29 Sep 2025 10:51:09 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp" alt="Capture JavaScript Integrity Metadata using CSP!"><p>Today we're announcing the open beta of a brand new and incredibly powerful feature on the Report URI platform, CSP Integrity! Having the ability to collect integrity metadata for scripts running on your site opens up a whole new realm of possibilities, and it couldn't be simpler to get started. If you have a few seconds spare, you have enough time to get started now!</p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/09/report-uri.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/09/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/09/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="a-revolution-for-content-security-policy">A revolution for Content Security Policy</h4><p>Content Security Policy (CSP) is an incredibly powerful mechanism, allowing you to leverage control over a wide range of resources on your site. You can control where resources are loaded from or where data is sent to, and you can be alerted when things don't go according to plan. But regular readers will already know all of the existing benefits of CSP, so today, let's focus on the latest addition to the CSP arsenal; the collection of integrity metadata.</p><p>It's so easy to get started that we can boil it down to two simple steps. </p><ol><li>Ask the browser to send integrity metadata for scripts.</li><li>Tell it where to send the integrity metadata for scripts.</li></ol><p></p><p>That's really all there is to it, so let's break it down. If you already have a CSP in place, you will need to add the <code>'report-sha256'</code> keyword to your <code>script-src</code> directive, and if you don't have a CSP in place, you can add one with just this directive. This is what your CSP <code>script-src</code> might look like after you add the new keyword, which I've highlighted in bold:</p>
<!--kg-card-begin: html-->
<pre>script-src 'self' some-cdn.com <b>'report-sha256'</b>;</pre>
<!--kg-card-end: html-->
<p></p><p>Now that you've added the new keyword to your policy, the browser will send integrity metadata for scripts that load on your site, but you will need to tell the browser where to send it, and for that, we will use the <a href="https://scotthelme.co.uk/introducing-the-reporting-api-nel-other-major-changes-to-report-uri/?ref=scotthelme.ghost.io" rel="noreferrer">Reporting API</a>. All you have to do to enable this is add a new response header:</p><pre><code>Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://scotthelme.report-uri.com/a/d/g"}],"include_subdomains":true}</code></pre><p></p><p>This defines a new group called <code>default</code> and sets the destination for telemetry data to <code>https://scotthelme.report-uri.com/a/d/g</code> . Please note that you should copy this value from the Setup page in <em>your</em> Report URI account as it will be unique to you and the one shown here is my unique URL for demonstration purposes. </p><p>With that, we can now link the two together by adding the <code>report-to</code> directive to your CSP:</p>
<!--kg-card-begin: html-->
<pre>script-src 'self' some-cdn.com 'report-sha256'; <b>report-to default;</b></pre>
<!--kg-card-end: html-->
<p></p><p>All you have to do now is sit back and wait for the telemetry to start rolling in!</p><p></p><h4 id="surfacing-this-in-the-ui">Surfacing this in the UI</h4><p>Processed in the same way that any telemetry sent to us would be, this data will only take a few minutes to start showing up in your dashboard. I've used our 'aggregate' search filter here to grab a list of unique dependencies that have been loaded on our site, and we can start to see the value we can extract from this data.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="1531" height="870" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/09/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/09/image.png 1000w, https://scotthelme.ghost.io/content/images/2025/09/image.png 1531w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If we focus on just one of these entries, we can see that a Bootstrap JS file has been loaded.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image-1.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="525" height="97"></figure><p></p><p>The browser has reported that the hash of the asset is <code>sha256-wMCQIK229gKxbUg3QWa544ypI4OoFlC2qQl8Q8xD8x8=</code>, which is helpful in various different ways. If we were loading this file from an external location for example, we could now track the hash over time to see if the file we are being served has changed. We can also use the hash to try and identify if the file we were served is the file we were expecting.</p><p></p><h4 id="identifying-verified-javascript-files">Identifying verified JavaScript files</h4><p>As you can see from the screenshot above, we have identified that the particular file loaded was a verified file from the 'bootstrap' package, hence the green marker with the library name. Using the hash as a fingerprint to uniquely identify a file is a common approach, but you need to have a reliable and <em>verified</em> library of fingerprints to reference against in order to lookup the fingerprint and know for sure. This is something that we've been working hard to build and so far we have verified fingerprints for <strong><em>4,268,847</em></strong> unique JS files from common packages, libraries and dependencies across the Web! As we have the fingerprints for each of those files stored in their sha256, sha 384 and sha512 variants, it means we have a running total of <strong><em>12,806,541</em></strong> fingerprints in our database so far! This is a huge amount of valuable information to draw from, and it's something that's available right there in the dashboard.</p><p>That's not all, though, there's something even better to add!</p><p></p><h4 id="identifying-javascript-vulnerabilities">Identifying JavaScript vulnerabilities</h4><p>You may notice that the marker for that file is green, and that means that there are no known issues reported with that particular version of that file/library. If you keep scrolling through your data, you might, if you're unlucky, come across a file that has a red marker instead...</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image-2.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="525" height="95"></figure><p></p><p>This particular file is from a version of the Bootstrap library that has a known security vulnerability, and we can now surface that information directly to you in our UI. If you click the warning, it will open a small dialog box to give you some quick information on what the minimum version of the library you need to upgrade to is, and it will even link out to the disclosure notice for the vulnerability.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image-3.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="527" height="223"></figure><p></p><p>This will be an awesome feature to have, allowing you to keep a close eye on exactly how problematic your JS dependencies are, and potentially avoiding costly issues from using dependencies with known vulnerabilities. </p><p></p><h4 id="looking-at-the-new-telemetry-payload">Looking at the new telemetry payload</h4><p>Defined in the specification as a <code>csp-hash</code> report, it has a very familiar JSON payload, similar to your typical CSP violation report. Here's a sample of one of our payloads:</p><p></p><pre><code>{
"csp-hash": {
"destination": "script",
"documentURL": "https://report-uri.com/login/",
"hash": "sha256-wMCQIK229gKxbUg3QWa544ypI4OoFlC2qQl8Q8xD8x8=",
"subresourceURL": "https://cdn.report-uri.com/libs/refresh/bootstrap/bootstrap.bundle.min.js",
"type": "subresource"
}
}</code></pre><p></p><p>Breaking the fields inside this payload down, it's fairly self-explanatory:</p><p><code>destination</code> - Only <code>script</code> is supported for now, with new destinations being planned for future updates.</p><p><code>documentURL</code> - This is the URL the browser was visiting when the reported resource was observed.</p><p><code>hash</code> - This is the hash of the resource, which in this case is the sha256 hash as that is what we requested.</p><p><code>subresourceURL</code> - This is the URL of the script that was requested and the hash is of the content of this file.</p><p><code>type</code> - Only <code>subresource</code> is supported for now, with the ability to expand to new types in the future.</p><p></p><p>Now, with the <code>'report-sha256'</code> keyword in place, one of these payloads will be sent for each script that loads on your page!</p><p></p><h4 id="quick-tips">Quick Tips</h4><p>I'm sure many people will have some questions that spring to mind after reading this, so let me address some of the most common ones that I encountered during our closed beta testing. </p><p></p><h6 id="do-i-need-to-build-a-full-csp">Do I need to build a full CSP?</h6><p>No! You don't need any CSP at all, you can start monitoring scripts and collecting integrity metadata right away:</p><pre><code>Content-Security-Policy-Report-Only: script-src 'report-sha256'; report-to default</code></pre><p></p><h6 id="is-integrity-metadata-sent-for-scripts-that-load-or-scripts-that-are-blocked">Is integrity metadata sent for scripts that load, or scripts that are blocked?</h6><p>Integrity metadata is only sent for scripts that load on your page. If a script is blocked, it is not requested, so there is no file to provide integrity metadata for.</p><p></p><h6 id="will-this-use-a-lot-of-my-event-quota">Will this use a lot of my event quota?</h6><p>No, we're going to downsample CSP Integrity reports for all customers at a rate of 1/10, so whilst it will of course use some event quota, it should not use excessive amounts.</p><p></p><h6 id="what-happens-after-the-open-beta">What happens after the open beta?</h6><p>CSP Integrity will become an add-on feature for customers on our Ultimate plan and customers on an Enterprise plan in 2026.</p><p></p><h4 id="get-started-now">Get started now</h4><p>I will be announcing a webinar to go through the new CSP Integrity feature so keep an eye out for that, but everything you need to get started is right here. It really is as simple as adding those two new values to your CSP and just waiting for the valuable data to start rolling in!</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[We're going High Availability with Redis Sentinel!]]></title>
<description><![CDATA[<p>We've just deployed some mega updates to our infrastructure at Report URI that will give us much more resilience in the future, allow us to apply updates to our servers even faster, and will probably go totally unnoticed from the outside!</p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/08/report-uri.png" class="kg-image" alt loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/08/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="our-previous-redis-setup">Our previous Redis setup</h4><p>I've</p>]]></description>
<link>https://scotthelme.ghost.io/were-going-high-availability-with-redis-sentinel/</link>
<guid isPermaLink="false">68976e9629f3b600011277d4</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[Redis]]></category>
<category><![CDATA[Sentinel]]></category>
<category><![CDATA[High Availability]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 18 Aug 2025 12:46:41 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp" alt="We're going High Availability with Redis Sentinel!"><p>We've just deployed some mega updates to our infrastructure at Report URI that will give us much more resilience in the future, allow us to apply updates to our servers even faster, and will probably go totally unnoticed from the outside!</p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/08/report-uri.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/08/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="our-previous-redis-setup">Our previous Redis setup</h4><p>I've talked about our infrastructure openly before, but I will give a brief overview of what we were working with and why I wanted to upgrade. We were using Redis in two places, one instance for our application session store, which is fairly self explanatory, and one instance for our inbound telemetry cache. Both of these isolated instances of Redis have been upgraded in the same way, but I will focus on the telemetry cache as this is the one that faces a significant amount of load! Here's how it works.</p><p>Visitors head to the websites of our customers, and they may or may not send some telemetry to us depending on whether there are any security, performance or other concerns to report to us about the website they were visiting. These telemetry events are received and are placed directly in to Redis because there are simply too many for us to process them in real-time. Our servers, that we refer to as 'consumers', will then feed telemetry from this Redis cache and process it, applying normalisation, filtering, checking it against our Threat Intelligence, and doing a whole heap of other work, before placing it in persistent storage, at which point it becomes available to customers in their dashboards. This 'ingestion pipeline' can take anywhere from 20-30 seconds during quiet periods, and spike up to 6-7 minutes during our busiest periods, but it is reliable and consistent. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-10.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="883" height="187" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-10.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-10.png 883w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If you'd like to see some real data about the volumes of telemetry we process, what our peaks and troughs look like, and a whole bunch of other interesting metrics, you should absolutely check out our <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">Public Dashboard</a>. That has everything from telemetry volumes, client types, regional traffic patterns, a global heat map and even a Pew Pew Map!!</p><p></p><h4 id="a-single-point-of-failure">A single point of failure</h4><p>Both of our caches, the session cache and the telemetry cache, were single points of failure, and it's never a good idea to have a single point of failure. We've never had an issue with either of them, but it's been something I've wanted to address for a while now. Not only that, but it makes updating and upgrading them much harder work too. The typical process for our upgrades has been to bring up a new server, fully patch and update it and then deploy Redis along with our configuration, with the final step being a data migration consideration. Using Ansible to do the heavy lifting makes this process easier, but not easy. The session cache of course needs a full data migration and then the application servers can flip from the old one to the new one, which isn't so bad because the data is relatively small. The telemetry cache is a little trickier because it involves pointing the inbound telemetry to the new cache, while allowing the consumers to drain down the old telemetry cache before moving them over too. As I say, it's not impossible, but it could be a lot better, and we still have that concern of them both being a single point of failure. </p><p></p><h4 id="redis-sentinel-to-the-rescue">Redis Sentinel to the rescue!</h4><p>You can read the official docs on <a href="https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/?ref=scotthelme.ghost.io" rel="noreferrer">Redis Sentinel</a> but I'm going to cover everything you need to know here if you'd like to keep reading. Redis Sentinel provides a few fundamental features that we're now leveraging to provide a much more resilient service. </p><p><strong>Monitoring:</strong> Redis Sentinels can monitor a pool of Redis caches to determine if they are healthy and available. </p><p><strong>Failover:</strong> Redis Sentinels will promote and demote Redis caches between the roles of Primary and Replica based on their health. </p><p><strong>Configuration:</strong> Clients connect to a Redis Sentinel to get the ip/port for the current Primary cache.</p><p></p><p>Leveraging these features, we've now created a much more robust solution that looks like this:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-8.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="1317" height="1034" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-8.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-8.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-8.png 1317w" sizes="(min-width: 720px) 720px"></figure><p></p><p>To give an overview of what's happening here, I will break it down in to the following:</p><p><strong>Redis Caches:</strong> These have an assigned role of either Primary or Replica, and for the purposes of our diagram, the red one will be the Primary and the yellow one will be the Replica. If you have more than two caches in your pool, there would be more Replicas. The role of the Replica is to keep itself fully aligned with the Primary so it can become the Primary at any point if needed. The role of the Primary is to serve the clients as their Redis cache and is where our inbound telemetry is being sent.</p><p><strong>Redis Sentinels:</strong> These severs do a few things, so let's break it down.</p><ol><li>They're providing ip/port information to clients about which cache is the current Primary. Clients don't know about the caches or how many there are, nor do they know which one is the Primary, so they first ask a Sentinel which cache is the current Primary and then connect to that one. Note that no cache activity passes through the Sentinels, all they do is provide the information to the client on where to connect, and the client will then connect directly to the cache.</li><li>They're monitoring the health of the Primary and Replica/s to detect any issues. If a problem is detected with the Primary, it is put to a vote to see if a Replica should be promoted to Primary. Our configuration requires a quorum of 3/4 votes to successfully promote a Replica to Primary, and demote the Primary to a Replica when it returns. Once that happens, the Sentinels will reconfigure all caches to follow the new Primary, and start providing the new Primary ip/port information to clients.</li><li>They're always monitoring for the addition of new caches or Sentinels. There is no configuration for how many Sentinels or caches there are, they are discovered by talking to the current Primary and getting information on how many Replicas and Sentinels are connected to that Primary.</li></ol><p></p><h4 id="failing-over">Failing over</h4><p>Of course, all of this work is designed to give us a much more resilient solution, and it will also make our work for installing updates and upgrades much easier too. There are two scenarios when the Sentinels can failover from a Primary to a Replica, and they are when the Sentinels detect an issue with the Primary and vote to demote it, or when we manually trigger a failover.</p><p>When we're getting ready to do some upgrades, the first step will be to take the Replica down so the Sentinels notice and remove it as an option for a current failover. We can then update/upgrade that server and bring it back online as a viable Replica. Now, when we want to upgrade the cache that is the current Primary, we trigger the failover and the Sentinels will take care of it, meaning our infrastructure now looks like this with the new roles.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-9.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="1308" height="1032" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-9.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-9.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-9.png 1308w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If there is a problem with the update/upgrade, we can immediately fail back, giving us a nice recovery option, and if everything goes well, we can now follow the same process of taking the Replica down for updates/upgrades. Once that's done, both servers are fully updated with considerably less work than before!</p><p></p><h4 id="other-considerations">Other considerations</h4><p>Whilst working through this project there were a few little details that I picked up along the way that might be useful to share, and also some that are probably a bit more specific to our deployment. </p><ul><li>Our Redis Sentinels are running on dedicated servers, but I saw many recommendations to co-locate a Redis Sentinel service alongside the Redis Server service on the cache servers themselves. I decided against this for a couple of reasons. First, I didn't want to lose a Sentinel when we took a cache down for updates/upgrades because this will impact the total number of Sentinels available to clients, and it has implications for voting too. Second, I wanted to take that load away from the caches so they could focus on being caches, meaning each of our servers is focusing on doing one thing well.</li><li>Your quorum for voting must be >50% of the number of Sentinels you have, it must be a majority vote to promote a new Primary. With our four Sentinels, if we had a quorum of two, we have less protection against something called a Split-Brain happening. As an example, let's say we have two caches, Redis Cache 1 and Redis Cache 2. Two of our four Sentinels have a transient network issue and see the Primary (Redis Cache 1) as being down, so they vote between themselves to promote a Replica (Redis Cache 2), and then begin that process. Because they can't communicate with the previous Primary (Redis Cache 1), they can't demote it to a Replica. Meanwhile, the other two Sentinels see no issue and continue as normal with their existing Primary (Redis Cache 1). You now have Sentinels providing ip/port information for two different Primaries that are both accepting writes from clients and their data is now diverging, a Split-Brain! Having a quorum require a majority of your Sentinels to vote doesn't fully solve this issue, but it does give you a lot more protection against it.</li><li>If you have a very read-heavy workload, your Replicas can share the load as they do support read-only connections. In our two scenarios we can't really leverage this benefit, which is a real shame, but being able to distribute reads across your replicas may be a huge benefit to you.</li></ul><p></p><h4 id="nobody-would-know">Nobody would know!</h4><p>Whilst this was quite a huge change on our side, from the outside, nobody would know anything about this if I hadn't published this blog post! Going forwards, this will allow me to sleep a lot better knowing we have a more resilient service, and that our future updates and upgrades will now take much less time and effort than before. If you have any feedback or suggestions on our deployment, feel free to drop by the comments below. </p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Automation improvements after a Tesla Powerwall outage!]]></title>
<description><![CDATA[<p>So, a weird thing happened over the last couple of days, and my Tesla Powerwalls weren't working properly, or, at all, actually... What's even more strange is that Tesla has been completely silent about this and hasn't made a single announcement about the issue</p>]]></description>
<link>https://scotthelme.ghost.io/automation-improvements-after-a-tesla-powerwall-outage/</link>
<guid isPermaLink="false">68974a0c29f3b6000112773f</guid>
<category><![CDATA[Tesla Powerwall]]></category>
<category><![CDATA[Home Assistant]]></category>
<category><![CDATA[Teslemetry]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 11 Aug 2025 09:49:48 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp" alt="Automation improvements after a Tesla Powerwall outage!"><p>So, a weird thing happened over the last couple of days, and my Tesla Powerwalls weren't working properly, or, at all, actually... What's even more strange is that Tesla has been completely silent about this and hasn't made a single announcement about the issue that I can find, and I haven't been notified.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-1.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="600" height="211" srcset="https://scotthelme.ghost.io/content/images/2025/08/image-1.png 600w"></figure><p></p><h4 id="my-home-setup">My Home Setup</h4><p>You can read various posts on my blog about how I automate almost all of my home with Home Assistant, and more recently there were two Tesla Powerwall posts [<a href="https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">1</a>][<a href="https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">2</a>] about bringing the final pieces of the puzzle together. I have everything working almost exactly how I want it to, with the one outstanding question on how I can control the charge rate of the Powerwalls, which I will be answering later!</p><p>First, though, this recent Tesla outage that I've seen absolutely nothing from Tesla about... Regular readers will know I automate my Powerwalls via <a href="https://www.home-assistant.io/?ref=scotthelme.ghost.io" rel="noreferrer">Home Assistant</a> using the <a href="https://teslemetry.com/?ref=scotthelme.ghost.io" rel="noreferrer">Teslemetry</a> service. </p><p></p><figure class="kg-card kg-image-card"><a href="https://teslemetry.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-2.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="731" height="76" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-2.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-2.png 731w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>I can control when the Powerwalls are charging or not based on the current cost for electricity import, and I can also control when they're exporting to the grid if I have excess capacity for the day. Things were going really quite well, until I woke up one morning and the batteries were basically empty. I woke up on the morning of the 7th August and we hadn't charged the batteries in the cheap off-peak tariff that we get overnight, which is really the main point of the batteries. Fill up on the cheap stuff overnight, avoid the expensive stuff during the day.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-3.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="485" height="345"></figure><p></p><p>I tried to enable grid charging to see if I could just get some power back in to the batteries so they weren't sat on such a low SoC, but it wouldn't enable. I wondered if Teslemetry were having an issue, but nothing was reported on their side. I disabled the integration and went directly to the official Tesla app and tried to enable grid charging there, but again, it wouldn't enable. After some searching, it seems it wasn't just me having the issue and anyone with a Powerwall setup seemed to be having the exact same problem. Shortly after, an incident went up on the Teslemetry <a href="https://status.teslemetry.com/incident/701944?ref=scotthelme.ghost.io" rel="noreferrer">status page</a> saying that the issue was with the Tesla API.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-4.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="834" height="517" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-4.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-4.png 834w" sizes="(min-width: 720px) 720px"></figure><p></p><p>It wasn't just Teslemetry, either. Other services that also allow for control of Tesla Powerwalls were having issues and reporting similar problems on their status pages too, like <a href="https://docs.netzero.energy/docs/tesla/2025-08/GridChargingIssue.html?ref=scotthelme.ghost.io" rel="noreferrer">NetZero</a>.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-5.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="1003" height="398" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-5.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-5.png 1003w" sizes="(min-width: 720px) 720px"></figure><p></p><p>As you can see from the graph above, I did manage to capture some excess solar production during the day and charge the batteries a little, but it was almost 24 hours later when the issue finally resolved and the button to enable grid charging started magically working again! I enabled the Teslemetry integration and we were back in business. </p><p></p><h4 id="so-whats-up-with-that">So what's up with that?..</h4><p>I genuinely do find it staggering that a trillion dollar company can't notify customers who are actively using their product of an issue such as this! It's pretty crazy when you think about it that there was an issue that effectively rendered Powerwalls useless, and there have been no notifications, updates, or a post-mortem, nothing... This is really poor from Tesla and I think this issue deserves more attention.</p><p>Part of me is also wondering if there might be a problem with enabling a huge amount of Powerwalls to be able to grid charge again all at once. Could it have an impact on the grid? Was that part of the problem and they staggered the rollout of the fix? What was the problem? I just want to know! </p><p><strong><em>Update 11th Aug 15:17 BST:</em></strong> Tesla responded to my support ticket with the following:</p><blockquote>We are reaching out regarding your inquiry that grid charging turns it self off automatically. Our App Team is aware of this issue and is currently working on resolving it.<br>Please rest assured, we will resolve it within the next Tesla App updates.</blockquote><p></p><p></p><h4 id="controlling-how-fast-your-powerwalls-charge">Controlling how fast your Powerwalls charge</h4><p>One of the last things I had to address in my previous blog post was controlling how fast my Powerwalls were charging. Once you set them to charge, they charge up at full power, which isn't really necessary. Charging them at such a high rate isn't necessary as I have a long time to charge them, and charging at a higher rate is worse for the battery, and it leaves it sat at 100% for a longer period overnight, which is also worse for the battery! Overall that's a bad pair of options, but what if you could change the rate at which the batteries draw power when charging? Well, you can!</p><p>This is super easy to do thanks to my use of Home Assistant and Teslemetry, and it's worth pointing out that one of the options below won't be available to you if you're not using a service like Teslemetry as it's not available in the Tesla app! It turns out that when the batteries are in a different mode, they charge at a different rate. Here is the operation mode you need to set in Teslemetry, and in brackets is the name of that mode in the Tesla app.</p><p></p><table>
<thead>
<tr>
<th>Operation Mode</th>
<th>Total Draw</th>
<th>Per Battery</th>
</tr>
</thead>
<tbody>
<tr>
<td>Autonomous (Time-Based Control)</td>
<td>15kW</td>
<td>5kW</td>
</tr>
<tr>
<td>Self-consumption (Self-Powered)</td>
<td>5.7kW</td>
<td>1.9kW</td>
</tr>
<tr>
<td>Backup (not available)</td>
<td>11.1kW</td>
<td>3.7kW</td>
</tr>
</tbody>
</table>
<p></p><p>I have 3 x Powerwalls so the above numbers show my total grid power draw when charging, and what the breakdown is per Powerwall. I've always been using "Autonomous" mode, which is known as "Time-Based Control" in the Tesla app, and that results in the batteries charging at their maximum rate of 5kW each!</p><p>It turns out that if you put the Powerwalls in "Self-consumption" mode, which is known as "Self-Powered" in the Tesla app, they only charge at 1.9kW each. That might be helpful to you, but for me, it's not enough. The Powerwalls have an official usable capacity of 13.5kWh, and mine are reporting a usable capacity of either 13.7kWh or 13.8kWh via the API. My cheap rate tariff applies from 23:30 to 05:30 giving me 6 hours and unfortunately, 6 hours x 1.9kW = 11.4kWh, which is not enough to fully recharge them.</p><p>The good news is that because I'm using the Teslemetry integration, it gives me access to the "Backup" mode, which isn't available in the Tesla app, and it sees the batteries charge at ~3.7kW, which works out quite well because 13.8kWh / 3.7kW = 3.7 hours (3hrs 45min). That means the batteries can charge at a lower rate and take a longer time to hit 100% SoC, but still be comfortably ready by 05:30 even if I need to go from 0% to 100%. Another bonus is that Backup mode will automatically set the Backup Reserve to 100% too! For comparison, here is the charging curve before and after the change, showing that slightly gentler curve now I'm using Backup mode.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/08/Screenshot-2025-08-09-152600.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="477" height="343"><figcaption><span style="white-space: pre-wrap;">Batteries charging at 15kW</span></figcaption></figure><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/08/Screenshot-2025-08-09-152614.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="475" height="342"><figcaption><span style="white-space: pre-wrap;">Batteries charging at 11.1kW</span></figcaption></figure><p></p><h4 id="my-current-automations">My current automations</h4><p>Given the above issue with the Tesla API, I wanted to avoid turning Grid Charging on and off because I might find myself in a position where I can't turn it back on again at some point in the future... Going forwards, I'm going to control charging by toggling the operation mode between Autonomous and Backup, whilst setting the Backup Reserve to the appropriate level too. I will also provide a copy of my export automation for completeness too. Here is the battery charging automation:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Dynamic Battery Charging Control
description: |
Sets Shireburn operation mode and backup reserve based on price and SoC.
- Cheap & below target SoC => backup @ 100%
- Expensive or above target SoC => autonomous @ target SoC
triggers:
- trigger: time_pattern
minutes: /5
conditions: []
actions:
- choose:
- conditions:
- condition: numeric_state
entity_id: >-
sensor.octopus_energy_electricity_snip_current_rate
below: input_number.electricity_cost_threshold
- condition: numeric_state
entity_id: sensor.shireburn_charge
below: input_number.powerwall_soc_threshold
sequence:
- condition: or
conditions:
- condition: template
value_template: "{{ states('select.shireburn_operation_mode') != 'backup' }}"
- condition: template
value_template: "{{ states('number.shireburn_backup_reserve') | int != 100 }}"
- action: select.select_option
target:
entity_id: select.shireburn_operation_mode
data:
option: backup
- action: number.set_value
target:
entity_id: number.shireburn_backup_reserve
data:
value: 100
- conditions:
- condition: numeric_state
entity_id: >-
sensor.octopus_energy_electricity_snip_current_rate
above: input_number.electricity_cost_threshold
sequence:
- condition: or
conditions:
- condition: template
value_template: >-
{{ states('select.shireburn_operation_mode') != 'autonomous'
}}
- condition: template
value_template: "{{ states('number.shireburn_backup_reserve') | int != 0 }}"
- action: select.select_option
target:
entity_id: select.shireburn_operation_mode
data:
option: autonomous
- action: number.set_value
target:
entity_id: number.shireburn_backup_reserve
data:
value: 0
mode: single
</code></pre><p></p><p>Here is the export automation:</p><p></p><pre><code class="language-yaml">alias: Dynamic Battery Export Control
description: >-
Adjust export setting and export mode display based on battery SoC and
time-of-day
triggers:
- minutes: /5
trigger: time_pattern
actions:
- variables:
now_ts: "{{ now().timestamp() }}"
today: "{{ now().date().isoformat() }}"
start_ts: "{{ (today ~ 'T05:30:00') | as_datetime | as_timestamp }}"
end_ts: "{{ (today ~ 'T23:30:00') | as_datetime | as_timestamp }}"
end_for_target_ts: "{{ (today ~ 'T23:15:00') | as_datetime | as_timestamp }}"
current_soc: "{{ states('sensor.shireburn_charge') | float(0) }}"
- choose:
- conditions:
- condition: template
value_template: "{{ now_ts < start_ts or now_ts > end_ts }}"
sequence:
- target:
entity_id: input_number.target_soc
data:
value: 100
action: input_number.set_value
- target:
entity_id: input_text.export_mode_state
data:
value: No Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'never' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: never
action: select.select_option
default:
- variables:
total_secs: "{{ end_for_target_ts - start_ts }}"
elapsed_secs: "{{ now_ts - start_ts }}"
progress: >-
{{ (elapsed_secs / total_secs) if elapsed_secs < total_secs else 1.0
}}
target_soc: "{{ 100 - (95 * (progress ** 1.5)) }}"
- target:
entity_id: input_number.target_soc
data:
value: "{{ target_soc | round(0) }}"
action: input_number.set_value
- choose:
- conditions:
- condition: template
value_template: |
{% set hour = now().hour %} {% if hour < 23 %}
{{ current_soc > (target_soc + 5) }}
{% else %}
{{ current_soc > target_soc }}
{% endif %}
sequence:
- target:
entity_id: input_text.export_mode_state
data:
value: Battery + Solar Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'battery_ok' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: battery_ok
action: select.select_option
- conditions:
- condition: template
value_template: "{{ current_soc < (target_soc - 5) }}"
sequence:
- target:
entity_id: input_text.export_mode_state
data:
value: No Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'never' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: never
action: select.select_option
default:
- target:
entity_id: input_text.export_mode_state
data:
value: Solar Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'pv_only' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: pv_only
action: select.select_option
mode: single
</code></pre><p></p><p>Hopefully they will be useful and perhaps we might know more about the Tesla API outage soon!..</p><p></p><p></p>
<!--kg-card-begin: html-->
<style>
pre[class*="language-"],
code[class*="language-"] {
font-size: 0.85em !important;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js" integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js" integrity="sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!--kg-card-end: html-->
]]></content:encoded>
</item>
<item>
<title><![CDATA[OWASP ASVS 5.0.0 is here!]]></title>
<description><![CDATA[<p>I've been a huge fan of OWASP for a very long time, having spoken at their conferences, contributed to their projects, consumed many of their resources and met some really awesome people along the way! Just recently, one of the very popular OWASP projects, the <a href="https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io" rel="noreferrer">Application Security Verification</a></p>]]></description>
<link>https://scotthelme.ghost.io/owasp-asvs-5-0-0-is-here/</link>
<guid isPermaLink="false">684ffc0ea1c29300018cdf43</guid>
<category><![CDATA[OWASP]]></category>
<category><![CDATA[ASVS]]></category>
<category><![CDATA[Report URI]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 04 Aug 2025 10:07:02 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg" alt="OWASP ASVS 5.0.0 is here!"><p>I've been a huge fan of OWASP for a very long time, having spoken at their conferences, contributed to their projects, consumed many of their resources and met some really awesome people along the way! Just recently, one of the very popular OWASP projects, the <a href="https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io" rel="noreferrer">Application Security Verification Standard</a> (ASVS), had a major update released and it brought with it some exciting changes. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/image-5.png" class="kg-image" alt="OWASP ASVS 5.0.0 is here!" loading="lazy" width="2000" height="500" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/image-5.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/06/image-5.png 1600w, https://scotthelme.ghost.io/content/images/size/w2400/2025/06/image-5.png 2400w" sizes="(min-width: 720px) 720px"></figure><p></p><h4 id="owasp-asvs">OWASP ASVS</h4><p>I'd bet that most people are familiar with the <a href="https://owasp.org/www-project-top-ten/?ref=scotthelme.ghost.io" rel="noreferrer">OWASP Top 10</a> project, one of the most notable flagship projects to come out of OWASP! </p><blockquote>The OWASP Top 10 is a standard awareness document for developers and web application security. It represents a broad consensus about the most critical security risks to web applications.</blockquote><p></p><p>For me, coming in at a close second place is the <a href="https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io" rel="noreferrer">OWASP ASVS</a> project, another project aimed at helping the community with a structured approach to taking care of application security.</p><blockquote>The OWASP Application Security Verification Standard (ASVS) Project provides a basis for testing web application technical security controls and also provides developers with a list of requirements for secure development.</blockquote><p></p><p>One of the things I love about the ASVS is the straightforward, "no nonsense" approach. They aren't requirements imagined in a vacuum, beyond the realistic hope or requirement of anyone trying to implement, instead they seek to be effective and balanced. This quote from the early paragraphs of the document in the 'Level evaluation' section, where you consider which level of compliance is appropriate for you, says a lot:</p><blockquote>Levels are defined by priority‑based evaluation of each requirement based on experience implement‑ ing and testing security requirements. The main focus is on comparing risk reduction with the effort to implement the requirement. Another key factor is to keep a low barrier to entry.</blockquote><p></p><p>I've provided a copy of the standard below and if you're not familiar with ASVS, I'd really recommend checking it out, even if only for you to use it as a reference rather than a hard requirement. </p><div class="kg-card kg-file-card"><a class="kg-file-card-container" href="https://scotthelme.ghost.io/content/files/2025/06/OWASP_Application_Security_Verification_Standard_5.0.0_en.pdf" title="Download" download><div class="kg-file-card-contents"><div class="kg-file-card-title">OWASP_Application_Security_Verification_Standard_5.0.0_en</div><div class="kg-file-card-caption"></div><div class="kg-file-card-metadata"><div class="kg-file-card-filename">OWASP_Application_Security_Verification_Standard_5.0.0_en.pdf</div><div class="kg-file-card-filesize">504 KB</div></div></div><div class="kg-file-card-icon"><svg viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg></div></a></div><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/06/Report-URI-Twitter-Card.png" class="kg-image" alt="OWASP ASVS 5.0.0 is here!" loading="lazy" width="800" height="400" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/Report-URI-Twitter-Card.png 600w, https://scotthelme.ghost.io/content/images/2025/06/Report-URI-Twitter-Card.png 800w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="the-parts-where-we-can-help">The parts where we can help</h4><p>As a platform focused on providing organisations with the tools they need to be secure, there is of course a huge overlap between the requirements in the OWASP ASVS 5.0.0 standard, and <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>'s wide range of products and features. Some of these requirements are more generalised and are something we can help with on a more holistic approach, and some of them are slam-dunk cases where Report URI can 100% help you meet that requirement. We'll go through these requirements at a high level and look at how Report URI can help, and we'll also provide additional guidance and context to help you understand the requirements better. Let's start with the big hitters first, and go from there, and also remember to check the compliance level which we will provide for each requirement to see if it's something you will need to consider. Here's a quick summary of the compliance levels if you aren't familiar with them.</p><p>✅ <strong>Level 1 — Informational / Low Assurance</strong><br>Low-risk apps (e.g. simple brochure sites or MVPs).</p><p><strong>✅✅ Level 2 — Standard Assurance (Baseline Compliance)</strong><br>Apps handling personal data, business logic, or moderate sensitivity.</p><p><strong>✅✅✅ Level 3 — High Assurance</strong><br>Financial systems, healthcare apps, and other high-value or high-impact systems.</p><p></p><p>You can assess which level of the requirements is suitable for your application, and then you can use that to guide you through the following sections to determine which apply to you and your application.</p><p></p><h4 id="v31-web-frontend-security-documentation">V3.1 Web Frontend Security Documentation</h4><p>This is a pretty sensible requirement to protect against the impact of malicious content find its way in to your site, and as a Level 1 requirement, is applicable to all sites.</p><p></p><p><strong>3.2.1 (Level 1)</strong></p><blockquote>Verify that security controls are in place to prevent browsers from rendering content or functionality in HTTP responses in an incorrect context (e.g., when an API, a user‑uploaded file or other resource is requested directly). Possible controls could include: not serving the content unless HTTP request header fields (such as Sec‑Fetch‑*) indicate it is the correct context, using the sandbox directive of the Content‑Security‑Policy header field or using the attachment disposition type in the Content‑Disposition header field.
</blockquote><p></p><p>Whenever you're using Content Security Policy, it's <em>always </em>advisable to monitor that policy and ensure there isn't anything happening that you weren't expecting. The sandbox directive is particularly powerful in the suggested context and allows you to isolate the downloaded content.</p><p></p><h4 id="v34-browser-security-mechanism-headers">V3.4 Browser Security Mechanism Headers
</h4><p>Honestly at this point in the realm of security online, who doesn't love Security Headers?! There is so much powerful protection you can leverage by configuring these relatively simple mechanisms, it's no wonder that they got an entire section in the standard. You should read this entire section, absolutely, but here are the parts we can help with specifically.</p><p></p><p><strong>3.4.3 (Level 2 and Level 3)</strong></p><blockquote>Verify that HTTP responses include a Content‑Security‑Policy response header field which defines directives to ensure the browser only loads and executes trusted content or resources, in order to limit execution of malicious JavaScript. As a minimum, a global policy must be used which includes the directives object‑src ‘none’ and base‑uri ‘none’ and defines either an allowlist or uses nonces or hashes. For an L3 application, a per‑response policy with nonces or hashes must be defined.</blockquote><p></p><p><strong>3.4.6 (Level 2)</strong></p><blockquote>Verify that the web application uses the frame‑ancestors directive of the Content‑Security‑Policy header field for every HTTP response to ensure that it cannot be embedded by default and that embedding of specific resources is allowed only when necessary. Note that the X‑Frame‑Options header field, although supported by browsers, is obsolete and may not be relied upon.</blockquote><p></p><p><strong>3.4.7 (Level 3)</strong></p><blockquote>Verify that the Content‑Security‑Policy header field specifies a location to report violations.</blockquote><p></p><p>That last one is particularly noteworthy, and I'd say that any application with a CSP in place should be monitoring for those violations because you want to know if things have been configured incorrectly, or worse, an attack is currently ongoing! All of that guidance is sensible though and these are all standard things that we'd be recommending to our customers anyway.</p><p></p><p><strong>V3.6 External Resource Integrity</strong></p><p>Another common risk being addressed by this requirement is that of your JavaScript supply chain. Over the years we have seen so many attacks where a website is compromised not because they were breached, but because one of their dependencies was breached instead! Whilst this requirement focuses specifically on the venerable Subresource Integrity (SRI), at the time of writing we have a new feature in beta that will go far beyond the capabilities of SRI and provide even more value. Reach out if you're interested in joining the beta!</p><p></p><p><strong>3.6.1 (Level 3)</strong></p><blockquote>Verify that client‑side assets, such as JavaScript libraries, CSS, or web fonts, are only hosted externally (e.g., on a Content Delivery Network) if the resource is static and versioned and Subresource Integrity (SRI) is used to validate the integrity of the asset. If this is not possible, there should be a documented security decision to justify this for each resource.</blockquote><p></p><h4 id="getting-started">Getting Started</h4><p>If you want to start the process of improving the security of your web application, you can get started with a free trial on our website right now, or reach out to our team if you'd like some support through this process. Maybe you're looking to avoid the risk of a costly data breach in the future, maybe you have some particular compliance requirement you're trying to meet, or perhaps you've identified a specific concern you'd like to address. Either way, we're here to help, and we have more than a decade of experience to draw from as we support you.</p><p>To achieve this, the ASVS standard is a great basis to work from, and I want to quote the following objectives from the standard itself:</p><p></p><blockquote>•<strong> Use as a metric</strong> - Provide application developers and application owners with a yardstick with which to assess the degree of trust that can be placed in their Web applications,<br>•<strong> Use as guidance</strong> - Provide guidance to security control developers as to what to build into security controls in order to satisfy application security requirements, and<br>•<strong> Use during procurement</strong> - Provide a basis for specifying application security verification requirements in contracts.</blockquote><p></p><p>No matter which angle you're coming from, I'm hoping to see increased adoption of the OWASP ASVS requirements over time and the benefits that will bring to security across the Web.</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀]]></title>
<description><![CDATA[<p>We’ve just passed a monumental milestone: <strong>2 trillion events processed</strong> through <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>!!! That’s <em>2,000,000,000,000</em> events for <a href="https://report-uri.com/products/content_security_policy?ref=scotthelme.ghost.io" rel="noreferrer">CSP</a>, <a href="https://report-uri.com/products/network_error_logging?ref=scotthelme.ghost.io" rel="noreferrer">NEL</a>, <a href="https://report-uri.com/products/dmarc_monitoring?ref=scotthelme.ghost.io" rel="noreferrer">DMARC</a>, and other browser-generated and email telemetry reports—ingested, parsed, and processed for our customers!</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image.png" class="kg-image" alt loading="lazy" width="565" height="215"></a></figure><p></p><p>This is a phenomenal milestone to achieve</p>]]></description>
<link>https://scotthelme.ghost.io/trillion-with-a-t-surpassing-2-trillion-events-processed/</link>
<guid isPermaLink="false">6863a159db89a500016be6a1</guid>
<category><![CDATA[Report URI]]></category>
<category><![CDATA[report-uri.com]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Wed, 02 Jul 2025 18:12:32 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀"><p>We’ve just passed a monumental milestone: <strong>2 trillion events processed</strong> through <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>!!! That’s <em>2,000,000,000,000</em> events for <a href="https://report-uri.com/products/content_security_policy?ref=scotthelme.ghost.io" rel="noreferrer">CSP</a>, <a href="https://report-uri.com/products/network_error_logging?ref=scotthelme.ghost.io" rel="noreferrer">NEL</a>, <a href="https://report-uri.com/products/dmarc_monitoring?ref=scotthelme.ghost.io" rel="noreferrer">DMARC</a>, and other browser-generated and email telemetry reports—ingested, parsed, and processed for our customers!</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="565" height="215"></a></figure><p></p><p>This is a phenomenal milestone to achieve in the year that will mark our 10th Birthday, and shows just how quickly we're still growing!</p><p><strong>May 2015</strong>: 0 Events - Launch Day [<a href="https://scotthelme.co.uk/csp-and-hpkp-violation-reporting-with-report-uri-io/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]<br><strong>March 2021</strong>: 500,000,000,000 Events - 500 Billion Events [<a href="https://www.facebook.com/reporturi/posts/pfbid0jZJQRbPsDZZsKnA21QzwQa7CcATd1AjD8Zwr4EPHeqytrDAC5hTjuSc8wbH5xuz2l" rel="noreferrer">source</a>]<br><strong>February 2022</strong>: 1,000,000,000,000 Events - 1 Trillion Events [<a href="https://x.com/Scott_Helme/status/1490066996268052494?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]<br><strong>November 2023</strong>: 1,500,000,000,000 Events - 1.5 Trillion Events [<a href="https://scotthelme.co.uk/report-uri-a-week-in-numbers-2023-edition/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]<br><strong>July 2025</strong>: 2,000,000,000,000 Events - 2 Trillion Events [keep reading!]</p><p></p>
<!--kg-card-begin: html-->
<a href="https://report-uri.com/?ref=scotthelme.ghost.io" style="box-shadow: none;">
<img src="https://scotthelme.co.uk/content/images/2025/07/report-uri---Copy.png" style="max-width: 75%;" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀">
</a>
<!--kg-card-end: html-->
<p></p><p>To mark this incredible occasion, I'm excited to announce the <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI Global Telemetry Dashboard</a>. I often get questions about how many telemetry events we process per day, how many browsers send us telemetry, where does the telemetry come from?! Well, now, you can see the answer to all of those questions and a lot more. If you like cool graphs, geeky data, and insights into things operating at enormous scale, you're really going to enjoy this! 😎</p><p></p><h4 id="the-report-uri-global-telemetry-dashboard">The Report URI Global Telemetry Dashboard</h4><p>Taking screenshots and sharing them here really isn't going to do this justice, and there is a special surprise on one of the dashboards that I'm not going to spoil for you here! If you want to see all of this stuff live and have a play around with it yourself, head over to the <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">dashboard</a> now. For those of you still here, I want to share some information and insights into the data. </p><p>As of the time of writing, this is the exact data showing on the homepage of the dashboard:</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-1.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="782" height="717" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-1.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-1.png 782w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>That incredible number of almost 2 trillion events processed is right there, and a pretty incredible average of over <em>12,000 events per second</em> that we processed yesterday! The thing that always strikes me about this number is that this isn't requests that we're serving a response to, nothing here can be served from cache. Each inbound event needs processing, analysing, and storing, with the potential for alerts to be raised, Threat Intelligence to be generated, customers to be notified and much, much more. Another impressive statistic is just how many unique clients out there are sending us telemetry data, reaching almost a quarter of a billion unique clients in just the last 30 days! That's a huge amount of insight into the Web ecosystem and a significant contributing factor to the benefit we can bring by leveraging that insight to spot attacks faster. The remainder of the <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">Dashboard Home</a> page shows additional insights into our traffic volumes, showing the hourly data for the last 24 hours, and the daily data for the last 7 and 30 days.</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-5.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="950" height="562" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-5.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-5.png 950w" sizes="(min-width: 720px) 720px"></a></figure><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-3.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="940" height="555" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-3.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-3.png 940w" sizes="(min-width: 720px) 720px"></a></figure><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-4.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="943" height="550" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-4.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-4.png 943w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>You can already start to see some interesting trends in that data, and these are trends that we've been tracking for a really long time. The 24 hour graph clearly shows the busy periods within a typical day for us, and it's not surprising to guess at what time of the day we see the most telemetry! Both the 7 day and 30 day graphs show our typical weekly trends too, with far more activity showing Mon-Fri than at the weekend. </p><p></p><h4 id="the-global-heat-map">The Global Heat Map</h4><p>This particular page in the dashboard is my second-favourite, and it's easy to see why. Looking at the telemetry we received over the last 30 days, this <a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io" rel="noreferrer">heat map</a> shows where that telemetry came from!</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-6.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1475" height="740" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-6.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-6.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-6.png 1475w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>You can zoom in to the map and really start to see where the busiest locations for us receiving telemetry are, with some expected locations showing up as the hottest areas of the map, and some more surprising areas showing up in there too! Zooming all the way in to the datacentre just down the road from me in Manchester, you can see exact counts of telemetry that went through that location in the last 30 days.</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-7.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="748" height="825" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-7.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-7.png 748w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>Have a fly around the map and see what your nearest location is and what volume of telemetry went through that location in the last 30 days on our <a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io" rel="noreferrer">Heat Map</a>!</p><p></p><h4 id="we-have-a-pew-pew-map">We have a Pew Pew Map!</h4><p>Okay okay I said I wasn't going to spoil the surprise, but I am way too excited about this one! Ever since seeing the original Norse Attack Map over 10 years ago, I've always wanted my own <a href="https://dash.report-uri.com/pewpewmap/?ref=scotthelme.ghost.io" rel="noreferrer">Pew Pew Map</a>, and now we have one! The GIF here is simply not going to do this justice, so you absolutely should head over to the page and see this thing live, in action, right now!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/pew-pew-map-1.gif" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1602" height="1015" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/pew-pew-map-1.gif 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/pew-pew-map-1.gif 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/07/pew-pew-map-1.gif 1600w, https://scotthelme.ghost.io/content/images/2025/07/pew-pew-map-1.gif 1602w" sizes="(min-width: 720px) 720px"></figure><p></p><p>What you're seeing here is <em>actual telemetry</em> being to sent to us! The map is 5 minutes behind real-time, and the data is real. The more red the zap is, the more telemetry we received from that location, and the faster the zap is moving, the more frequently we are receiving telemetry from that location! There are no estimations here, there is no mock data. This is a direct representation of real data, and I think that just makes this even more awesome! Our primary datacentre is located in SFO, as I'm sure you've guessed, and we do have a secondary location in AMS for our more regulated EU customers, but they are excluded from this data. </p><p>I'm tempted to get myself a second monitor off to the side of my desk just for this map, it really is quite cool to watch. If you come back at different times during the day, you can clearly see the different trends in our traffic patterns for inbound telemetry, to the point where you can almost track the Sun moving across the map based on the telemetry volumes!</p><p></p><h4 id="country-specific-data">Country Specific Data</h4><p>There's just no way you can top a Pew Pew Map, but we do still have a heap of interesting data that we can peruse. The next page is our <a href="https://dash.report-uri.com/country/?ref=scotthelme.ghost.io" rel="noreferrer">Country Specific Data</a>, which in many ways shows exactly what you might expect, with a couple of surprises in there too.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-8.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="850" height="493" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-8.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-8.png 850w" sizes="(min-width: 720px) 720px"></figure><p></p><p>The US is by far our largest source of telemetry data, which makes a lot of sense as a large portion of our customers are US-based companies, likely with US based visitors themselves. People may be surprised to see Japan so high up that list, but we do have a few quite notable customers in Japan, including a rather large coffee company, so they generate more than their fair share of telemetry. After that, the UK is coming in, followed by India that are helped by their population size, and then a broad selection of countries that you might expect to see up there too. The table below that gives exact details on our Top 20 Countries, but here are the top 3 for you.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-9.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="754" height="305" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-9.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-9.png 754w" sizes="(min-width: 720px) 720px"></figure><p></p><h4 id="some-technical-data">Some technical data!</h4><p>To close things out for now, we have our <a href="https://dash.report-uri.com/info/?ref=scotthelme.ghost.io" rel="noreferrer">Detailed Information</a> dashboard that goes into a little bit more of the technical data behind our inbound telemetry. We do have plans to expand this to incorporate a lot more metrics, but for now, I think we have a pretty good selection available. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-10.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1027" height="1153" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-10.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-10.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-10.png 1027w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Looking at the HTTP Version and TLS Protocol used, you can see that we clearly have a very wide selection of modern clients out there, using TLSv1.3 almost exclusively, and HTTP/2 and HTTP/3 taking the lion's share of the application protocol. When responding to inbound telemetry requests, our most common response is an empty txt response to save as much bandwidth and overhead as possible, we simply reply with an empty 201 to acknowledge receipt. After the 201, we have a variety of response codes that we might use along with a message in the body to give more information on the problem to help with debugging. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-11.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1025" height="1145" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-11.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-11.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-11.png 1025w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Taking a look across the next set of data, I'm often quite surprised to see Firefox so heavily represented in the Client UA, but there are a wider selection of Chromium based browsers in the mix. The front runners for Top Colo By Requests won't be a huge surprise given the country data above, but it is nice to see a breakdown of where our most popular locations are. Closing it out, we have a little more client data showing the Client OS, with a probably expected Windows and iOS being the most dominant, and Client Type showing that Desktop is hanging on to the top spot with Mobile close behind!</p><p></p><h4 id="theres-more-to-come">There's more to come!</h4><p>I created this dashboard to scratch an itch, I got to play with some cool data, and as I mentioned above, I've <em>always</em> wanted a pew pew map! I also really enjoyed stepping away from the more serious responsibilities of being 'CEO and Founder' to play around with cool technology and build something awesome! That is, after all, why I founded the company in the first place. </p><p>I hope the dashboard will be interesting and provide some insight into what we're doing, and if there's something you'd like to see added to the dashboard, or any questions you have, just let me know in the comments below. As I come across any interesting metrics in the future, I'll be sure to add them to the dashboard, and I'd love to hear your suggestions too!</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!]]></title>
<description><![CDATA[<p>In my <a href="https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">first blog post about hacking my Tesla Powerwalls</a>, I laid out all of the foundations and information about my home energy setup. You really need to read that blog post first as I'm going to be building on all of that work here, and assuming that</p>]]></description>
<link>https://scotthelme.ghost.io/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/</link>
<guid isPermaLink="false">6849e889cca6290001a6e931</guid>
<category><![CDATA[Home Assistant]]></category>
<category><![CDATA[Teslemetry]]></category>
<category><![CDATA[Tesla Powerwall]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Fri, 13 Jun 2025 10:52:02 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!"><p>In my <a href="https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">first blog post about hacking my Tesla Powerwalls</a>, I laid out all of the foundations and information about my home energy setup. You really need to read that blog post first as I'm going to be building on all of that work here, and assuming that you're familiar with everything in that post.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/image.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="432" height="132"></figure><p></p><h4 id="the-final-piece-of-the-puzzle">The final piece of the puzzle</h4><p>In my previous post, I had focused on solving a particular problem. My Powerwalls would charge up overnight on the very cheap off-peak rate so that they could run my house during the day, avoiding the expensive peak rate. The problem I was coming across was that the batteries would hit 100% SoC overnight, start depleting during the day, but when the sun started shining, our solar array could fill the batteries back up to 100% SoC and then export excess solar production at an almost worthless cost. I created an automation to stop the batteries from charging once they hit 75% SoC, which left plenty of room for excess solar production to fill up the batteries during the day rather than exporting the energy. This was an improvement, but it wasn't an ideal solution, so I set out to resolve it. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/OE-Logo-RGB_Unstacked-white-xl-octopus--1--min-1.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="350" height="207"></figure><p></p><h4 id="my-new-export-tariff">My new export tariff</h4><p>I've finally managed to get myself on a decent export tariff, meaning that I'm now being paid much more for energy that I export to the grid. I feel that energy I export to the grid should be paid for at the same rate as energy I import from the grid, but that's unfortunately not the case. I am, however, now being paid almost <em>4x more</em> than I was before for energy export, and it still falls 50% short of what my import costs! My import tariff is still the same, and my export tariff is now much better.</p><p></p><table>
<thead>
<tr>
<th>Activity</th>
<th>Time</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td>Import</td>
<td>05:30 to 23:30</td>
<td>£0.28/kWh</td>
</tr>
<tr>
<td>Import</td>
<td>23:30 to 05:30</td>
<td>£0.07/kWh</td>
</tr>
<tr>
<td>Export</td>
<td>00:00 to 23:59</td>
<td><s>£0.04/kWh</s> <em><strong>£0.15/kWh</strong></em></td>
</tr>
</tbody>
</table>
<p></p><p>Of course, the fact that I can purchase electricity for £0.07/kWh overnight and then export it for £0.15/kWh is quite an attractive proposition, and something that I'd like to take advantage of. This is something known as load-shifting, or <a href="https://en.wikipedia.org/wiki/Load_management?ref=scotthelme.ghost.io" rel="noreferrer">Load Management</a>, where suppliers want to encourage people to move their usage into more desirable time periods, and is the reason for the very existence of the cheap overnight tariffs in the first place. I already have the batteries and solar array to allow me to do this, so all I needed was a solution to automate it.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/image-4.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="521" height="259"></figure><p></p><h4 id="using-home-assistant-and-teslemetry-again">Using Home Assistant and Teslemetry, again!</h4><p>In my previous post, I used <a href="https://teslemetry.com/?ref=scotthelme.ghost.io" rel="noreferrer">Teslemetry</a> to control when the batteries could charge from the grid to introduce the ceiling of 75% SoC to leave room for excess solar production during the day. Now, though, I want to go one step further and charge the batteries to 100% SoC overnight, and slowly let them feed back to the grid during the day if we have excess capacity, along with excess solar production, to leave me with enough power to get back to the cheap rate at 23:30 again.</p><p></p><p>The <a href="https://www.home-assistant.io/?ref=scotthelme.ghost.io" rel="noreferrer">Home Assistant</a> automation to do this was surprisingly simple, so I will post the whole thing here, and then we can talk through what it does.</p><p></p><pre><code class="language-yaml">alias: Dynamic Battery Export Control
description: >-
Adjust export setting and export mode display based on battery SoC and
time-of-day
triggers:
- minutes: /15
trigger: time_pattern
actions:
- variables:
now_ts: "{{ now().timestamp() }}"
today: "{{ now().date().isoformat() }}"
start_ts: "{{ (today ~ 'T05:30:00') | as_datetime | as_timestamp }}"
end_ts: "{{ (today ~ 'T23:30:00') | as_datetime | as_timestamp }}"
end_for_target_ts: "{{ (today ~ 'T23:15:00') | as_datetime | as_timestamp }}"
current_soc: "{{ states('sensor.shireburn_charge') | float(0) }}"
- choose:
- conditions:
- condition: template
value_template: "{{ now_ts < start_ts or now_ts > end_ts }}"
sequence:
- target:
entity_id: input_number.target_soc
data:
value: 100
action: input_number.set_value
- target:
entity_id: input_text.export_mode_state
data:
value: No Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'never' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: never
action: select.select_option
default:
- variables:
total_secs: "{{ end_for_target_ts - start_ts }}"
elapsed_secs: "{{ now_ts - start_ts }}"
progress: >-
{{ (elapsed_secs / total_secs) if elapsed_secs < total_secs else 1.0
}}
target_soc: "{{ 95 - (85 * (progress ** 1.5)) }}"
- target:
entity_id: input_number.target_soc
data:
value: "{{ target_soc | round(0) }}"
action: input_number.set_value
- choose:
- conditions:
- condition: template
value_template: |
{% set hour = now().hour %} {% if hour < 23 %}
{{ current_soc > (target_soc + 5) }}
{% else %}
{{ current_soc > target_soc }}
{% endif %}
sequence:
- target:
entity_id: input_text.export_mode_state
data:
value: Battery + Solar Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'battery_ok' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: battery_ok
action: select.select_option
- conditions:
- condition: template
value_template: "{{ current_soc < (target_soc - 5) }}"
sequence:
- target:
entity_id: input_text.export_mode_state
data:
value: No Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'never' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: never
action: select.select_option
default:
- target:
entity_id: input_text.export_mode_state
data:
value: Solar Export
action: input_text.set_value
- condition: template
value_template: "{{ states('select.shireburn_allow_export') != 'pv_only' }}"
- target:
entity_id: select.shireburn_allow_export
data:
option: pv_only
action: select.select_option
mode: single
</code></pre><p></p><p></p><p>The automation runs every 15 minutes and first checks if we're in the off-peak window of 23:30 to 05:30, and if we are, it sets the Target SoC to 100% and that's it, we want the battery charging. If we're in the peak window of 05:30 to 23:30, then a few things happen.</p><p>The first thing it does is calculate how far through the peak window we are, which is used to calculate the Target SoC. Originally, I had a linear decline throughout the day which seemed like it should work just fine. If we were 25% of the way through the window, the Target SoC would be 75%, at 50% of the way through the window the Target SoC was 50%, and 75% of the way through the window, the Target SoC would be 25%.</p><p>Plotting that out it looks like the graph below, and it did work fine, but we had some evenings where our usage would spike quite high and there wasn't enough of a buffer left in the battery, leaving us short on battery power. I didn't want to have to worry about problems like this, and whilst it worked fine for the most part, it needed improving.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/06/Screenshot-2025-06-11-213247.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="478" height="340"><figcaption><span style="white-space: pre-wrap;">Target SoC with a linear decline</span></figcaption></figure><p></p><p>I changed the Target SoC to an exponential decline throughout the day, meaning it would hold a slightly higher SoC during the day, but then tail off quite hard at the end of the day, making the Target SoC curve look like this. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/06/Screenshot-2025-06-11-213303.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="480" height="340"><figcaption><span style="white-space: pre-wrap;">Target SoC with an exponential decline</span></figcaption></figure><p></p><p>This gave us much more breathing room if we had a sudden increase in usage during the evening, and if not, the batteries could just export at a more aggressive rate to reach the lower Target SoC. This worked much better for us, gave us the breathing room we needed, but still meant that we could export any spare capacity during the peak window.</p><p>Now that the Target SoC is established, we can decide on the export mode to keep the Actual SoC aligned with the Target SoC. For that, there are the following 3 options.</p><p></p><p><strong>Condition 1:</strong> Battery SoC > (Target SoC + 5) = Export Battery + Solar<br><strong>Condition 2:</strong> Battery SoC < (Target SoC - 5) = Export Nothing<br><strong>Default:</strong> Export Solar Only<br><br></p><p>Condition 1: If we're more than 5% above target, we should export from the solar array and the batteries as we have spare capacity in the batteries. This will bring the Actual SoC down to the Target SoC faster as time goes by.</p><p>Condition 2: If we're more than 5% below target we should export nothing, as we're trying to preserve the battery capacity by using solar to power the house as much as possible. This reduces the load on the battery and allows the Target SoC to fall over time while holding the Actual SoC as high as possible.</p><p>Default: The aim here is to run the house on the batteries and export solar production to the grid, trying to maintain a steady decline in Actual SoC throughout the day that should align it with Target SoC.</p><p>If you look at the graphs above for Actual SoC, you can see the step changes every 15 minutes when the automation runs and changes the export mode to adjust the Actual SoC as needed, keeping things well aligned! One thing I am keeping in mind is that we're in summer right now, so our solar production is quite good and our energy usage is probably on the lower end of what's typical. As we approach autumn and then winter, I'm wondering if I might need to make the Target SoC curve a little steeper, to hold a higher charge during the day and tail off more aggressively in the evening, to give us a little more breathing room. Only time will tell on that one, but if there's a change required, I will come back and update this post with a note.</p><p></p><h4 id="seeing-it-in-action">Seeing it in action</h4><p>Now that I've run this automation for over a week, it's had a chance to run through days where we have exceptionally low solar production (British Summer), and we export very little, to days like yesterday where we have amazing solar production all day (one day in British Summer), and as a result, we see some great export rates too!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6398-1.jpeg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="600" height="979" srcset="https://scotthelme.ghost.io/content/images/2025/06/IMG_6398-1.jpeg 600w"></figure><p></p><p>The top graph shows our export for the day and you can clearly see the Solar Bell (the solar production bell curve) from sunrise to sunset. Dotted throughout the day you can also see the occasional spikes in our export, where the automation has detected excess capacity in the batteries and released it into the grid, causing a spike in our 30 minute export window. You can then clearly see the exponential decline for battery capacity kicking in as we release excess battery capacity late into the evening. The bottom graph then shows our import, which looks very different, hauling in that cheap power overnight and not importing any energy during the day. If you want to see those graphs as kWh instead of cost, here they are.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6399-1.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="600" height="966" srcset="https://scotthelme.ghost.io/content/images/2025/06/IMG_6399-1.jpg 600w"></figure><p></p><h4 id="maximum-import-maximum-export">Maximum import, maximum export</h4><p>Once we hit the cheap tariff at 23:30, the heavy import begins. Not only do our Powerwalls begin charging, capable of pulling in a total of 15kW, we have quite a few other systems that make use of the off-peak rate too. If either one of our two EVs are plugged in, they will begin charging at 7kW, the hot tub only heats and filters the water overnight, so that fires up using almost 4kW, and a variety of smaller appliance like the tumble dryer might be waiting too, using a few more kW of power.</p><p>This means that we can quite easily hit our maximum import limit of 24kW, at which point the Powerwalls will automatically throttle their import to prevent us from exceeding the limit and blowing our 100A main fuse (100A x 240v = 24,000W). Given that we have a 24kW import limit, you'd think that we have a 24kW export limit too, but, we don't... For reasons known only to them, our Distribution Network Operator determined that we are only able to export at a maximum rate of 3.869kW, which you can see on the negative portion of the graph during the day where we hit the flat bottom of our maximum export rate. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6400.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1290" height="764" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/IMG_6400.jpg 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/IMG_6400.jpg 1000w, https://scotthelme.ghost.io/content/images/2025/06/IMG_6400.jpg 1290w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Side note: Yes, I know our nominal voltage on the grid in the UK is 230v after we harmonised with Europe, but in reality, it's often much higher. My 100A fuse is also BS1361 rated, meaning it can realistically carry 120A indefinitely, and last for prolonged periods at 125A. Even at our minimum tolerance on grid voltage of 207v, we'd still only be pulling 116A at 24kW (24,000w / 207v = 116A). Here's the grid voltage for the same time window shown in the above graph to show what I mean.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6401.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1290" height="734" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/IMG_6401.jpg 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/IMG_6401.jpg 1000w, https://scotthelme.ghost.io/content/images/2025/06/IMG_6401.jpg 1290w" sizes="(min-width: 720px) 720px"></figure><p></p><p>It is interesting to see what effect solar PV has on the peak voltage, getting us quite high at 255v, and then at the opposite end of the scale under heavy load, we can dip all the way down to 233v!</p><p></p><h4 id="can-we-control-the-powerwall-charge-rate">Can we control the Powerwall charge rate?</h4><p>Just before we wrap up, there is one final thing that I haven't yet found a solution for that I would like to resolve, so if anyone has any ideas, please let me know in the comments. When the Powerwalls start charging from the grid, they charge at their absolute maximum possible rate of 5kW per battery, giving us our total import rate of 15kW into our 3 batteries. Each battery has a usable capacity of 13.5kWh, so, ignoring efficiency for easy maths, at maximum charging rate they can go from 0% to 100% in 2 hours and 45 minutes.</p><p>It seems unnecessary to charge at their absolute maximum rate which will have them sat at 100% SoC for more than half of our off-peak window, and have charged them at full power when it wasn't needed. I'd much prefer to be able to set them to charge at 2.5kW per battery, which will still allow them to go from 0% to 100% during our off-peak window. Charging them more slowly is better for the batteries in the long run due to the lower charge rate, and, they will spend less time at 100% SoC which is also better for their health. If I had dynamic control of the charge rate I could even calculate what rate was required for them to hit 100% SoC right as the off-peak window ends! </p><p>At present, I can't see any way to do this, either setting a static import limit per battery or a dynamic limit based on SoC and time remaining. I know I can restrict the overall site import limit with the Tesla One app, but that's not useful here as it isn't flexible and could result in no import into the batteries if something else is using the import! I'm a little stuck on this one, do you have any ideas?</p><p></p><p></p>
<!--kg-card-begin: html-->
<style>
pre[class*="language-"],
code[class*="language-"] {
font-size: 0.85em !important;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js" integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js" integrity="sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!--kg-card-end: html-->
]]></content:encoded>
</item>
<item>
<title><![CDATA[Shorter certificates are coming!]]></title>
<description><![CDATA[<p>Well, I was certainly hoping for this result, but wasn't necessarily expecting it! I'm pleased to report that Ballot SC-081v3 passed, and that shorter certificate lifetimes are now coming!</p><p></p><h4 id="the-schedule">The Schedule</h4><p>I will go into more detail later in the post, but right now, let'</p>]]></description>
<link>https://scotthelme.ghost.io/shorter-certificates-are-coming/</link>
<guid isPermaLink="false">6807855b51addd000148f995</guid>
<category><![CDATA[TLS]]></category>
<category><![CDATA[PKI]]></category>
<category><![CDATA[Certificate Authorities]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Tue, 22 Apr 2025 13:37:29 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp" alt="Shorter certificates are coming!"><p>Well, I was certainly hoping for this result, but wasn't necessarily expecting it! I'm pleased to report that Ballot SC-081v3 passed, and that shorter certificate lifetimes are now coming!</p><p></p><h4 id="the-schedule">The Schedule</h4><p>I will go into more detail later in the post, but right now, let's cover the important details! Here is the schedule that was proposed and voted on, and will now come into effect:</p><p></p><table>
<thead>
<tr>
<th><strong>Certificate issued on or after</strong></th>
<th><strong>Certificate issued before</strong></th>
<th><strong>Maximum Validity Period</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>March 15, 2026</td>
<td>398 days</td>
</tr>
<tr>
<td>March 15, 2026</td>
<td>March 15, 2027</td>
<td>200 days</td>
</tr>
<tr>
<td>March 15, 2027</td>
<td>March 15, 2029</td>
<td>100 days</td>
</tr>
<tr>
<td>March 15, 2029</td>
<td></td>
<td>47 days</td>
</tr>
</tbody>
</table>
<p></p><p>This means that the important dates are as follows:</p><p><strong>March 15th 2026</strong>: <em>All new certificates capped at 200 days validity</em></p><p><strong>March 15th 2027</strong>: <em>All new certificates capped at 100 days validity</em></p><p><strong>March 15th 2029</strong>: <em>All new certificates capped at 47 days validity!</em></p><p></p><p>It's a little sad that the dates slipped since the initial proposal of this ballot, hence the 'v3', but still, we now have a definitive date in the future when we know these things will be happening.</p><p>If we look at the progress of shortening certificates over time, we can see that the trend fits well, and if anything, our progress had stalled out a little, but we're now back on track, even if at a reduced pace.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/validity-period-final-data.png" class="kg-image" alt="Shorter certificates are coming!" loading="lazy" width="1227" height="715" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/validity-period-final-data.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/validity-period-final-data.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/validity-period-final-data.png 1227w" sizes="(min-width: 720px) 720px"></figure><p></p><p>You can see my views on the progress of this ballot in my earlier post, <a href="https://scotthelme.co.uk/are-shorter-certificates-finally-coming/?ref=scotthelme.ghost.io" rel="noreferrer">Are shorter certificates finally coming?!</a>.</p><p></p><h4 id="who-voted-for-what">Who voted for what?</h4><p>As the CA/B Forum votes are public, we can take a look at who voted for what, and in the past, that has given us some interesting insight into the sentiment across industry. If you want to view the official announcement, you can do that <a href="https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/9768xgUUfhQ?ref=scotthelme.ghost.io" rel="noreferrer">here</a>, but here are the results.</p><p></p><blockquote>The voting period for <strong>SC-081v3: Introduce Schedule of Reducing Validity and Data Reuse Periods</strong> has completed. The ballot has: <strong>PASSED<br><br>Voting Results<br><br>Certificate Issuers</strong><br><br>30 votes in total:<br> * 25 voting YES: Amazon, Asseco Data Systems SA (Certum), Buypass AS, Certigna (DHIMYOTIS), Certinomis, DigiCert, Disig, D-TRUST, eMudhra, Fastly, GlobalSign, GoDaddy, HARICA, iTrusChina, Izenpe, NAVER Cloud Trust Services, OISTE Foundation, Sectigo, SHECA, SSL.com, SwissSign, Telia Company, TrustAsia, VikingCloud, Visa<br> * 0 voting NO:<br> * 5 ABSTAIN: Entrust, IdenTrust, Japan Registry Services, SECOM Trust Systems, TWCA<br><br><strong>Certificate Consumers</strong><br><br>4 votes in total:<br> * 4 voting YES: Apple, Google, Microsoft, Mozilla<br> * 0 voting NO:<br> * 0 ABSTAIN:</blockquote><p></p><p>That is absolutely phenomenal support, and I think largely due to the open nature of the proposal, seeking to gather industry feedback and adapting the proposal based on that feedback. It's a little disappointing to see CAs abstain from voting, especially IdenTrust, but here are the responses from those CAs who abstained and provided a <a href="https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/bvWh5RN6tYI?ref=scotthelme.ghost.io" rel="noreferrer">reason</a>:</p><p></p><blockquote>IdenTrust abstains from voting on Ballots SC-081v3.<br><br>While we agree with all proposals related to TLS DV validation and support full automation, we remain unconvinced of the security benefits of further reducing the validity period for OV and EV TLS certificates. </blockquote><p></p><p>It's interesting that the opposition was not to reducing the validity of DV certificates, which can be easily automated, but to OV and EV certificates instead. It's widely known what my views on OV and EV certificates are ([<a href="https://scotthelme.co.uk/gone-for-ever/?ref=scotthelme.ghost.io" rel="noreferrer">1</a>][<a href="https://scotthelme.co.uk/are-ev-certificates-worth-the-paper-theyre-written-on/?ref=scotthelme.ghost.io" rel="noreferrer">2</a>][<a href="https://scotthelme.co.uk/looks-like-a-duck-swims-like-a-duck-qwacs-like-a-duck-probably-an-ev-certifiacate/?ref=scotthelme.ghost.io" rel="noreferrer">3</a>] etc...), but if we're assuming that those certificates have too much manual process involved, and thus require human intervention and are inherently unfriendly to automation, it's really just another nail in the coffin.</p><p></p><blockquote>JPRS abstains from voting on Ballot SC-081.<br><br>We agree with the value of reducing certificate validity periods.<br>We also believe that this ballot indicates to subscribers the inevitability of automation and represents a significant step forward for the WebPKI ecosystem.<br><br>However, we will abstain from this ballot.<br>While we will do our best to support our subscribers and hope that subscribers and other stakeholders in the WebPKI ecosystem will be able to follow the proposed timeline, it is currently uncertain whether it can realistically be achieved.<br><br>In addition, we hope that discussions will take place even after this ballot when necessary, taking into account the actual state of migration in the WebPKI ecosystem.</blockquote><p></p><p>It's good to see JPRS acknowledging the enormous benefit that this ballot brings, and I can also understand and appreciate the hesitation around the ecosystem being ready. Whilst adoption of automated solutions for certificate management has been going well, it's not been going well enough. With that said, there has also never been a requirement for people to adopt those solutions, it was simply a good idea to do so. Now that there is a requirement to have these solutions in place, and a generous 4 year window to get them in place, I'm hoping we start to see an increase in the rate of adoption and progress. </p><p></p><blockquote>TWCA "ABSTAINS" from voting on ballot SC-081v3.<br><br>1. We agree that shortening the certificate validity period can reduce cybersecurity risks, and we also acknowledge the advantages of automated certificate management.<br><br>2. We believe that reducing the validity period from 100 days to 47 days is too drastic. Compared to the harm caused by phishing websites, there is currently no way to demonstrate to users how high the cybersecurity risk would be if the certificate validity period were 100 days.<br><br>3. For some websites that already use certificates, if they ultimately fail to deploy automated certificate management, they might abandon HTTPS in favor of HTTP (given that in today’s browser UIs, HTTP only displays a red warning in the upper left corner, whereas improper management leading to certificate expiration results in warnings across the entire page).</blockquote><p></p><p>Again, it's great to see a CA acknowledge the value that these shorter certificates will bring, but after this, I start to struggle with this response. It's really not clear to me what point is being made in comment #2, but I'm not sure what relevance phishing websites have here, and I don't know what cybersecurity risk is present if a certificate is only valid for 100 days! For comment #3, I completely disagree. There's absolutely no reasonable concern that websites are going to move back to HTTP from HTTPS. </p><p></p><blockquote>SECOM Trust Systems ABSTAINS from voting on Ballot SC-081v3.<br><br>We agree with the value of shortening certificate validity periods, and we support this direction in the long term.<br><br>In addition, we believe this ballot signals to subscribers that automation is inevitable, and that this is a great step forward for the WebPKI ecosystem.<br><br>On the other hand, we are also aware that some subscribers may experience costs to adapt.<br><br>In our business practice, we think it is important to be able to explain not only the value but also the cost and explain that our approach is net-positive for the web, unless there is a reasonable alternative.<br><br>Assuming a tradeoff between the period to prepare and the cost, we do not have an answer to shortening it to 47 days instead of 90 days.<br><br>Therefore, we abstain.</blockquote><p></p><p>Lastly, SECOM are another CA acknowledging the enormous benefits that this proposal will bring, but I'm struggling to agree with their argument. It seems that they are suggesting that if certificates were valid for 90 days, then subscribers could/would renew them manually, but a validity period of 47 days would be too short for manual renewal to be practical. I'd make the argument that even at 90 days validity, manual renewal is completely impractical and should already be fully automated, so this doesn't make any real difference at all.</p><p></p><h4 id="come-and-geek-out-with-me">Come and geek out with me</h4><p>If you really want to know more about this whole TLS/PKI ecosystem, why these changes are coming, and the years of history that are driving this, I'd really recommend you check out our training course:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image-4.png" class="kg-image" alt="Shorter certificates are coming!" loading="lazy" width="1012" height="466" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/image-4.png 1012w" sizes="(min-width: 720px) 720px"></figure><p></p><p>There are full details of what we cover on the <a href="https://www.feistyduck.com/training/practical-tls-and-pki?ref=scotthelme.co.uk" rel="noreferrer">training page</a>, but if you want to get hands on with TLS and PKI, fully deploy HTTPS to your own server, including getting a real certificate, build your own CA, and so much more, you should really get involved and join us! It's a fairly intermediate level course, I probably wouldn't recommend it for beginners, but it contains everything you're going to need from a practical perspective, and we have plenty of opportunity to talk about and understand current affairs too.</p><p></p>]]></content:encoded>
</item>
<item>
<title><![CDATA[Hacking my Tesla Powerwalls to be the ultimate home energy solution!]]></title>
<description><![CDATA[<p>I've had solar and batteries at home for quite some time now, and despite my experience with them being really awesome, there were a few little things that were bugging me. Using systems from various different suppliers doesn't always provide the perfect integration, so I hacked</p>]]></description>
<link>https://scotthelme.ghost.io/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/</link>
<guid isPermaLink="false">68021b1ded448d0001f0d432</guid>
<category><![CDATA[Home Assistant]]></category>
<category><![CDATA[Tesla Powerwall]]></category>
<category><![CDATA[Teslemetry]]></category>
<dc:creator><![CDATA[Scott Helme]]></dc:creator>
<pubDate>Mon, 21 Apr 2025 11:58:28 GMT</pubDate>
<media:content url="https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp" medium="image"/>
<content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!"><p>I've had solar and batteries at home for quite some time now, and despite my experience with them being really awesome, there were a few little things that were bugging me. Using systems from various different suppliers doesn't always provide the perfect integration, so I hacked together my own!</p><p></p><h4 id="no-not-that-kind-of-hacking">No, not that kind of hacking!</h4><p>I have done that kind of hacking over the years, and many times I've blogged about it right here, from <a href="https://scotthelme.co.uk/the-vulnerable-web-api-for-my-nissan-leaf/?ref=scotthelme.ghost.io" rel="noreferrer">cars</a> to <a href="https://scotthelme.co.uk/hacking-iot-cameras/?ref=scotthelme.ghost.io" rel="noreferrer">cctv cameras</a> and <a href="https://scotthelme.co.uk/ee-brightbox-router-hacked/?ref=scotthelme.ghost.io" rel="noreferrer">routers</a> to <a href="https://scotthelme.co.uk/hotel-hippo-insecure/?ref=scotthelme.ghost.io" rel="noreferrer">hotel websites</a>, but this blog post isn't about that type of hacking, it's about what I consider to be the more traditional definition of the term. This is the type of hacking where you get something to perform a function that it wasn't intended to perform, where you take something and make it better than it was by tinkering with it. Think more 'life hack' than 'computer hack', and you're heading along the right lines here. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="432" height="132"></figure><p></p><h4 id="the-problem">The problem</h4><p>I have a few different system at home that all are related to power consumption, and whilst they are all 'smart', they don't fully integrate. Here's the overview:</p><p></p><ul><li>☀️Solar Panels: We have 14 Perlight solar panels managed by Enphase that make up the 4.2kWp array on our roof, and they produce energy when the sun shines. This part is pretty straightforward!</li><li>🔋Tesla Powerwalls: We have 3x Tesla Powerwall 2 in our garage that were purchased to help us load-shift our energy usage. Electricity is very expensive in the UK and moving from peak usage which is 05:30 to 23:30 at £0.27/kWh, to off-peak usage, which is 23:30 - 05:30 at £0.07/kWh, is a significant cost saving.</li><li>💡Smart Tariff: My wife and I both drive electric cars and our electricity provider, Octopus Energy, has a Smart Charging tariff. If we plug in one of our cars, and cheap electricity is available, they will activate the charger and allow us to use the off-peak rate, even at peak times. </li></ul><p></p><p>All of this is great, but it does present a few issues:</p><p>1) The main issue is that the Powerwalls will always fill up to 100% on the off-peak rate overnight, aiming to maximise how much power they can supply us during the peak-rate, load shifting the maximum possible amount and saving the most money. That's great, but it doesn't factor in that we have a 4.2kWp solar array on our roof. Once the sun rises and we start generating solar power, the batteries are still full, or almost full, and we quickly begin exporting energy to the grid. Our export tariffs in the UK are terrible and our supplier only pays us a measley £0.04/kWh for exported energy, which is a complete waste! I want a way to tell the Powerwalls to only charge to, let's say, 80% on grid power, and leave headroom for solar generation during the day. No such feature exists. </p><p></p><p>2) The next issue is that the Powerwalls know about our cheap off-peak tariff overnight from 23:30 to 05:30, but not the flexibility of our Smart Charging tariff during peak times. That means that if we plug one of our cars in during the day to top them up, the Powerwalls think we're on peak rates and will charge the car from the batteries and not the grid. I'd like the Powerwalls to be able to respond to our current energy price, rather than a fixed time schedule. No such feature exists. </p><p></p><p>3) The final issue is similar to the above, but about our Powerwall batteries rather than the car batteries. If we have a window of cheap electricity from our electricity supplier, then the Powerwalls should top themselves up when rates are cheap. Not only does this feature not exist, but it also needs to factor in point #1 about not overfilling the batteries and leaving room for solar generation. Argh!</p><p></p><h4 id="home-assistant-and-teslemetry-to-the-rescue">Home Assistant and Teslemetry to the rescue!</h4><p>I've talked about Home Assistant a <em>lot</em> and we use it extensively throughout our house. From lights to appliances, monitoring, alarm systems and more, Home Assistant has a finger in almost every pie! The Tesla Powerwalls integrate nicely with Home Assistant and provide all of their information which I've formulated into a nice dashboard. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="1080" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>I can keep a track of the State of Charge (SoC), power coming in and out of the batteries, power coming in and out of the grid, and much more!</p><p></p><p>Of course, we also have an integration for our solar array too, and the Enphase integration allows us to see exactly what's going on with our solar array, and all of the statistics you'd ever want. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="1026" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Finally, our energy supplier has a pretty cool API, and there's an integration for that in Home Assistant too! We can see a whole bunch of different data about our gas and electricity supply, and, crucially, the current rate we're being charged for electricity!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="913" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>So, I now have all of the information that I could possibly need in one place, but there was one final piece of the puzzle to solve. The Tesla Powerwall integration in Home Assistant does not allow for control over the Tesla Gateway or Tesla Powerwall devices themselves, it simply retrieves information. No problem, though, because after some quick Googling, I found the <a href="https://developer.tesla.com/?ref=scotthelme.ghost.io" rel="noreferrer">Tesla Fleet API</a>. </p><blockquote>Fleet API is a data and command service providing access to Tesla vehicles and energy devices. Developers can interact with their own devices, or devices for which they have been granted access by a customer.</blockquote><p></p><p>The Fleet API would allow me to have the level of control over my Powerwalls that I needed, and there are a bunch of API clients out there that I could get up and running and build something from. Rather than getting knee deep in building something when my time is a little tight right now, I thought I'd take a look at what solutions exist already, and I quickly found Teslemetry.</p><p></p><p></p>
<!--kg-card-begin: html-->
<img src="https://scotthelme.co.uk/content/images/2025/04/teslemetry.svg" class="kg-image" style="max-width:400px;" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!">
<!--kg-card-end: html-->
<p></p><p></p><h4 id="using-teslemetry">Using Teslemetry</h4><p>The Teslemetry <a href="https://teslemetry.com/pricing?ref=scotthelme.ghost.io" rel="noreferrer">subscription</a> is super cheap, at £2.26/mo including taxes, and it came with a free trial period, and a Home Assistant integration, so it seemed like a winner. At this price it's really not worth spending any time on writing my own code, and I can support the service if it provides a benefit. Getting it set up was a breeze, logging in to my Tesla account and granting it access to only the Powerwalls and not the car, and then hooking that up to the Home Assistant integration. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="913" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>As you can see, this is no longer just information coming from the Powerwalls, I now have the ability to control settings on the Powerwalls too! This is what's going to help me create the automations that I need to ensure the Powerwalls behave how I want them to behave.</p><p></p><h4 id="capping-the-battery-power-when-grid-charging">Capping the battery power when grid charging!</h4><p>The first automation I wanted to create was to limit how high the batteries will charge when charging from the grid. The primary benefit here would be to limit the SoC on the overnight charge, so that once the solar starts generating, there would be room in the battery to store than, rather than export it to the grid. There may also be another benefit here in preventing the batteries from hitting 100% SoC and then sitting at 100% SoC for a prolonged period of time, which can be bad for their health over the long term. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1600" height="908" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 1600w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This automation is set to run every 30 minutes, at the top and bottom of the hour, as that's the time window that my electricity rate can change. Here's the YAML for the automation:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Disable Grid Charging
description: ""
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: state
entity_id: switch.shireburn_allow_charging_from_grid
state: "on"
- condition: or
conditions:
- condition: numeric_state
entity_id: >-
sensor.octopus_energy_electricity_1_snip_3_current_rate
above: 0.11
- condition: numeric_state
entity_id: sensor.shireburn_charge
above: 75
actions:
- action: switch.turn_off
metadata: {}
data: {}
target:
entity_id: switch.shireburn_allow_charging_from_grid
mode: single
</code></pre><p></p><p>So, every 30 minutes, I'm checking to see if the cost of electricity is high, or the battery is above 75% SoC, and if so, we're disabling grid charging. This means the battery will only charge when electricity is cheap and the SoC is below the target of 75%. In these summer months where we are producing plenty of solar power, I can keep the target SoC on grid charging at 75%, leaving plenty of room to store solar generation, and in the winter months, I will likely set the target SoC back to 100% as we will have little solar generation and will need all of the battery backup we can get. </p><p>The automation to enable grid charging is then of course very similar, checking for energy cost and SoC target, and then enabling grid charging when required.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1597" height="912" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 1597w" sizes="(min-width: 720px) 720px"></figure><p></p><p>And here's the YAML for that one too:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Enable Grid Charging
description: ""
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: state
entity_id: switch.shireburn_allow_charging_from_grid
state: "off"
- condition: numeric_state
entity_id: sensor.shireburn_charge
below: 75
- condition: or
conditions:
- condition: time
after: "23:30:00"
before: "05:30:00"
weekday:
- mon
- tue
- wed
- thu
- fri
- sat
- sun
- condition: numeric_state
entity_id: >-
sensor.octopus_energy_electricity_1_snip_3_current_rate
below: 0.11
enabled: true
actions:
- action: switch.turn_on
metadata: {}
data: {}
target:
entity_id: switch.shireburn_allow_charging_from_grid
mode: single
</code></pre><p></p><p>This has worked out really well and is doing exactly what we need it to do. If we look at the SoC on the batteries over the last 30 days, you can see when I enabled this automation as they're no longer hitting 100% SoC on the overnight charge, and you can also see a few days before the change when solar managed to charge the batteries back up to 100% and then we ended up exporting to the grid. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1600" height="722" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 1600w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Apart from some testing on the two nights where I let them charge back to 100% SoC, you can see that the automation is now keeping the batteries at a much better maximum SoC. </p><p></p><h4 id="responding-to-dynamic-pricing">Responding to dynamic pricing</h4><p>When we had the Powerwalls and the solar array installed, we didn't have a dynamic pricing tariff. Our plan was for the solar production and battery power to carry us from 05:30 all the way through to 23:30, completely avoiding the peak costs, and load shifting our entire usage into the off-peak window. That works perfectly well and we don't need to worry about responding to the dynamic pricing from a cost standpoint. </p><p>The other thing that's changed for us is that we now have two electric cars using the EV charger at home. Both of our cars are programmed to only charge during the off-peak window of 23:30 to 05:30, and this hasn't caused any problems for us so far either. We don't do enough driving that we've ever worried about both of us needing to charge at the same time, nor during peak time. The other consideration is that the cars will charge from the Powerwall batteries if they think the grid power is expensive, and ideally we want the cars to draw power from the grid, not the batteries. </p><p>All of that said, it'd still be nice to have the ability to respond to the dynamic pricing and absorb some power from the grid when it's cheap, and charge the cars on grid power rather than battery power if we ever wanted to. The question is, how do I do that? This isn't something the Powerwall is designed to do either.</p><p></p><h4 id="utilising-powerwall-settings-to-achieve-our-goal">Utilising Powerwall settings to achieve our goal</h4><p>Looking at the settings that are surfaced through Teslemetry, there isn't a huge amount of options to go at, so it was easy enough to explore them and see if any of them could be used to do what I need. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image-1.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="407" height="514"></figure><p></p><p>I'm already leveraging the 'Allow charging from grid' setting as we've seen above, and the 'Allow Export' and 'Storm Watch' aren't going to be useful here. That leaves the 'Operation Mode' and 'Backup Reserve' options to explore. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image-2.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="415" height="582"></figure><p></p><p>The Operation Mode options can be summarised as:</p><p></p><ul><li>Autonomous - Uses the time settings to load-shift as I've covered so far and does that fully automatically.</li><li>Backup - Saves 100% of the power for backups during grid outage, which isn't really useful to us. </li><li>Self Consumption - The focus is on charging from solar and using 100% of that power to run the house.</li></ul><p></p><p>This means that Autonomous mode is the correct setting for us as it achieves what we set out to achieve with the battery, leaving us the Backup Reserve option to play with, which is the one that sounds like it can do what we need. </p><p>The purpose of Backup Reserve is to set the minimum SoC that the battery will drop down to during operation, always saving the amount specified for grid outages to keep you online. As you can see from my screenshots, we've always had this set to 0% as we want to use the full capacity of the batteries to load shift, and we aren't too concerned about having reserves for if the grid goes down. </p><p>What I found during testing was that if you use the Backup Reserve and Grid Charging options together, you can create some specific behaviour that results in exactly what we need. Here's a table that gives an overview of the scenarios I'm looking at, with the main one highlighted in bold:</p><p></p><table>
<thead>
<tr>
<th>Battery SoC %</th>
<th>Backup Reserve SoC %</th>
<th>Grid Charging</th>
<th>Battery Charging</th>
<th>Power Draw</th>
</tr>
</thead>
<tbody>
<tr>
<td>50%</td>
<td>0%</td>
<td>Disabled</td>
<td>No</td>
<td>From Battery</td>
</tr>
<tr>
<td>50%</td>
<td>75%</td>
<td>Disabled</td>
<td>No</td>
<td>From Grid</td>
</tr>
<tr>
<td>50%</td>
<td>0%</td>
<td>Enabled</td>
<td>No</td>
<td>From Battery</td>
</tr>
<tr>
<td><strong>50%</strong></td>
<td><strong>75%</strong></td>
<td><strong>Enabled</strong></td>
<td><strong>Yes</strong></td>
<td><strong>From Grid</strong></td>
</tr>
<tr>
<td>80%</td>
<td>0%</td>
<td>Disabled</td>
<td>No</td>
<td>From Battery</td>
</tr>
<tr>
<td>80%</td>
<td>75%</td>
<td>Disabled</td>
<td>No</td>
<td>From Battery</td>
</tr>
<tr>
<td>80%</td>
<td>0%</td>
<td>Enabled</td>
<td>No</td>
<td>From Battery</td>
</tr>
<tr>
<td>80%</td>
<td>75%</td>
<td>Enabled</td>
<td>No</td>
<td>From Battery</td>
</tr>
</tbody>
</table>
<p></p><p>By controlling the Backup Reserve %, we can control whether the battery is discharging or not. If the Backup Reserve % is lower than the SoC %, the battery will discharge, and if the Backup Reserve % is higher than the SoC %, the battery will not discharge.</p><p>Alongside that, we can also control if the battery is actively charging from the grid, rather than just allowing our house to draw its power from the grid, using the Grid Charging control. We can now use these combinations of settings to get the Powerwalls to do exactly what we want! In summary:</p><p>The Powerwalls will always charge to 75% when electricity is cheap, meaning we always have spare capacity to absorb solar production, and we can use the car charger any time we like.</p><p></p><p>Here are the YAML configurations for the automations, of which there are now 4, and I've converted them to use variables for the cost and SoC thresholds so they're not hardcoded in the automations:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Disable Grid Charging
description: ""
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: state
entity_id: switch.shireburn_allow_charging_from_grid
state: "on"
- condition: or
conditions:
- condition: numeric_state
entity_id: >-
sensor.octopus_energy_electricity_1_snip_3_current_rate
above: input_number.electricity_cost_threshold
- condition: numeric_state
entity_id: sensor.shireburn_charge
above: input_number.powerwall_soc_threshold
actions:
- action: switch.turn_off
metadata: {}
data: {}
target:
entity_id: switch.shireburn_allow_charging_from_grid
mode: single
</code></pre><p></p><pre><code class="language-yaml">alias: Powerwall - Enable Grid Charging
description: ""
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: state
entity_id: switch.shireburn_allow_charging_from_grid
state: "off"
- condition: numeric_state
entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate
below: input_number.electricity_cost_threshold
enabled: true
- condition: numeric_state
entity_id: sensor.shireburn_charge
below: input_number.powerwall_soc_threshold
actions:
- action: switch.turn_on
metadata: {}
data: {}
target:
entity_id: switch.shireburn_allow_charging_from_grid
mode: single
</code></pre><p></p><pre><code class="language-yaml">alias: Powerwall - Set Backup Reserve To 0%
description: ""
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: numeric_state
entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate
above: input_number.electricity_cost_threshold
- condition: template
value_template: "{{ states('number.shireburn_backup_reserve') | int != 0 }}"
actions:
- action: number.set_value
metadata: {}
data:
value: "0"
target:
entity_id: number.shireburn_backup_reserve
mode: single
</code></pre><p></p><pre><code class="language-yaml">alias: Powerwall - Set Backup Reserve To 75%
description: ""
triggers:
- trigger: time_pattern
minutes: /30
conditions:
- condition: numeric_state
entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate
below: input_number.electricity_cost_threshold
- condition: template
value_template: >-
{{states('number.shireburn_backup_reserve') | int !=
states('input_number.powerwall_soc_threshold') | int}}
actions:
- action: number.set_value
metadata: {}
data:
value: "75"
target:
entity_id: number.shireburn_backup_reserve
mode: single
</code></pre><p></p><p>We've had these running for a few days now and, as far as I can see, they seem to be behaving exactly how I want them to behave! That said, if you're a Powerwall expert, Home Assistant expert, or have just spotted something that I can do be doing better, drop by the comments below and let me know!</p><p></p><p><strong><em>Update 13th June 2025</em></strong>: There is now a follow up to this post with further improvements!</p><p><a href="https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!</a></p><p></p><p></p>
<!--kg-card-begin: html-->
<style>
pre[class*="language-"],
code[class*="language-"] {
font-size: 0.85em !important;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer">
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js" integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js" integrity="sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!--kg-card-end: html-->
]]></content:encoded>
</item>
</channel>
</rss>
<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Scott Helme]]></title><description><![CDATA[Hi, I'm Scott Helme, a Security Researcher, Entrepreneur and International Speaker. I'm the creator of Report URI and Security Headers, and I deliver world renowned training on Hacking and Encryption.]]></description><link>https://scotthelme.ghost.io/</link><image><url>https://scotthelme.ghost.io/favicon.png</url><title>Scott Helme</title><link>https://scotthelme.ghost.io/</link></image><generator>Ghost 6.14</generator><lastBuildDate>Wed, 28 Jan 2026 09:36:56 GMT</lastBuildDate><atom:link href="https://scotthelme.ghost.io/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us]]></title><description><![CDATA[<p>Dogfooding is often talked about as a best practice, but I don't often see the results of such activities. For all new features introduced on Report URI, we are always the first to try them out and see how they work. In this post, we'll look</p>]]></description><link>https://scotthelme.ghost.io/eating-our-own-dogfood-what-running-report-uri-on-report-uri-taught-us/</link><guid isPermaLink="false">69638d1da2de7d0001fa2bb3</guid><category><![CDATA[Report URI]]></category><category><![CDATA[Content Security Policy]]></category><category><![CDATA[Integrity Policy]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Wed, 28 Jan 2026 09:36:48 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us"><p>Dogfooding is often talked about as a best practice, but I don't often see the results of such activities. For all new features introduced on Report URI, we are always the first to try them out and see how they work. In this post, we'll look at a few examples of issues that we found on Report URI using Report URI, and how you can use our platform to identify exactly the same kind of problems!</p><p></p><h4 id="dogfooding">Dogfooding</h4><p>If you're not familiar with the term dogfooding, or 'eating your own dogfood', here's how the Oxford English Dictionary defines it:</p><p></p><blockquote>Computing slang<br>Of a company or its employees: to use a product or service developed by the company, as a means of testing it before it is made available to customers.</blockquote><p></p><p>It's pretty straightforward and something that we've been doing quite literally since the dawn of Report URI over a decade ago. Any new feature that we introduce gets deployed on Report URI first of all, prompting changes, improvements or fixes as required. Once things are going well, we then introduce a small selection of our customers to participate in a closed beta, again to illicit feedback for the same improvement cycle. After that, the feature will go to an open beta, and finally on to general availability. We currently have two features in the final open beta stages of this process, <a href="https://scotthelme.co.uk/capture-javascript-integrity-metadata-using-csp/?ref=scotthelme.ghost.io" rel="noreferrer">CSP Integrity</a> and <a href="https://scotthelme.co.uk/integrity-policy-monitoring-and-enforcing-the-use-of-sri/?ref=scotthelme.ghost.io" rel="noreferrer">Integrity Policy</a>, and it was during the dogfooding stage of these features that some of things we're doing to discuss were found.</p><p></p><h4 id="integrity-policyfinding-scripts-missing-sri-protection">Integrity Policy - finding scripts missing SRI protection</h4><p>If you're not familiar with Subresource Integrity (SRI), you can read my blog post on <a href="https://scotthelme.co.uk/subresource-integrity/?ref=scotthelme.ghost.io" rel="noreferrer">Subresource Integrity: Securing CDN loaded assets</a>, but here's is a quick explainer. When loading a script tag, especially from a 3rd-party, we have no control over the script we get served, and that can lead to some pretty big problems if the script is modified in a malicious way. </p><p>SRI allows you to specify an integrity attribute on a script tag so that the browser can verify the file once it downloads it, protecting against malicious modification. Here's a simple example of a before and after for a script tag without SRI and then with SRI.</p><pre><code>//before <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"> </script> //after <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha256-ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM=" crossorigin="anonymous"> </script></code></pre><p></p><p>There have been literally countless examples where SRI would have saved organisations from costly attacks and data breaches, including that time when <a href="https://scotthelme.co.uk/protect-site-from-cryptojacking-csp-sri/?ref=scotthelme.ghost.io" rel="noreferrer">governments all around the World got hacked</a> because they didn't use it. That said, it can be difficult to enforce the use of SRI and make sure all of your script tags have it, until <a href="https://scotthelme.co.uk/integrity-policy-monitoring-and-enforcing-the-use-of-sri/?ref=scotthelme.ghost.io" rel="noreferrer">Integrity Policy</a> came along. You can now trivially ensure that all scripts across your application are loaded using SRI by adding a simple HTTP Response Header.</p><pre><code>Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(default)</code></pre><p></p><p>This will ask the browser to send a report for any script that is loaded without using SRI, and then, of course, we can go and fix that!</p><p>Here's a couple that we'd missed. First, we had this one come through.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-16.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1742" height="100" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-16.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-16.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2026/01/image-16.png 1600w, https://scotthelme.ghost.io/content/images/2026/01/image-16.png 1742w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This is interesting because it really should have SRI as something in the account section, and it turns out it almost did, but there was a typo!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-15.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1449" height="243" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-15.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-15.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-15.png 1449w" sizes="(min-width: 720px) 720px"></figure><p></p><p>That's an easy fix, and we found another script without SRI a little later too.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-13.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1225" height="375" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-13.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-13.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-13.png 1225w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This script was in our staff admin section and it was missing SRI, it was reported, and we fixed it!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-14.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1447" height="147" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-14.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-14.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-14.png 1447w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Another great thing to consider about this is that you will only receive telemetry reports when there's a problem. If everything is running as it should be on your site, then no telemetry will be sent!</p><p></p><h4 id="content-security-policyfinding-assets-that-shouldnt-be-there">Content Security Policy - finding assets that shouldn't be there</h4><p>The whole point of CSP is to get visibility into what's happening on your site, and that can be what assets are loading, where data is being communicated, and much more. Often, we're looking for indicators of malicious activity, like JavaScript that shouldn't be present, or data being somewhere it shouldn't be. But, sometimes, we can also detect mistakes that were made during development!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-18.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1495" height="163" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-18.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-18.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-18.png 1495w" sizes="(min-width: 720px) 720px"></figure><p></p><p>We started getting reports for these images loading on our site and because they're not from an approved source, they were blocked and reported to us. Looking closely, the images are from our <code>.io</code> domain instead of our <code>.com</code> domain, which is what we use in test/dev environments, but not in production. It seems that someone had inadvertently hardcoded a hostname that they should not have hardcoded and our CSP let us know!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-17.png" class="kg-image" alt="Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us" loading="lazy" width="1452" height="103" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-17.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-17.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-17.png 1452w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Another simple fix for an issue detected quickly and easily using CSP.</p><p></p><h4 id="but-normally-we-dont-find-anything">But normally we don't find anything!</h4><p>Of course, you're only ever going to find a problem by deploying our product if you had a problem to find in the first place. Our goal is always to test these features out and make sure they're ready for our customers, but sometimes, we do happen to find issues in our own site.</p><p>I guess that's really part of the value proposition though, the difference between <em>thinking</em> you don't have a problem and <em>knowing</em> you don't have a problem. Whether or not we'd found anything by deploying these features, we'd have still massively improved our awareness because we could then be confident we didn't have those issues. </p><p>It just so happens that we didn't think we had any problems, but it turns out we did! Do you think you don't have any problems on your site?</p><p></p>]]></content:encoded></item><item><title><![CDATA[Blink and you'll miss them: 6-day certificates are here!]]></title><description><![CDATA[<p>What a great way to start 2026! Let's Encrypt have now made their short-lived certificates <a href="https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability?ref=scotthelme.ghost.io" rel="noreferrer">available</a>, so you can go and start using them right away.</p><p>It wasn't long ago when the <a href="https://scotthelme.co.uk/shorter-certificates-are-coming/?ref=scotthelme.ghost.io" rel="noreferrer">announcement</a> came that by 2029, all certificates will be reduced to a maximum of</p>]]></description><link>https://scotthelme.ghost.io/blink-and-youll-miss-them-6-day-certificates-are-here/</link><guid isPermaLink="false">694688d8ab4aac00016ee79e</guid><category><![CDATA[Let's Encrypt]]></category><category><![CDATA[Google Trust Services]]></category><category><![CDATA[TLS]]></category><category><![CDATA[Short-Lived Certificates]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 19 Jan 2026 10:48:24 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp" alt="Blink and you'll miss them: 6-day certificates are here!"><p>What a great way to start 2026! Let's Encrypt have now made their short-lived certificates <a href="https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability?ref=scotthelme.ghost.io" rel="noreferrer">available</a>, so you can go and start using them right away.</p><p>It wasn't long ago when the <a href="https://scotthelme.co.uk/shorter-certificates-are-coming/?ref=scotthelme.ghost.io" rel="noreferrer">announcement</a> came that by 2029, all certificates will be reduced to a maximum of 47 days validity, and here we are already talking about certificates valid for less than 7 days. Let's Encrypt continue to drive the industry forwards and considerably exceed the reasonable expectations of today.</p><p></p><h4 id="getting-a-short-lived-certificate">Getting a short-lived certificate</h4><p>Of course, what you want to know is how to get one of these certificates! How you do this will change slightly depending on which tool you're using, but you need to specify the <code>shortlived</code> certificate profile when requesting your certificate from Let's Encrypt.</p><p>I'm using <a href="https://acme.sh/?ref=scotthelme.ghost.io" rel="noreferrer">acme.sh</a> and here is the command I used to get one of these certs when I started playing with this last year:</p><pre><code>acme.sh --issue --dns dns_cf -d six-days.scotthelme.co.uk --force --keylength ec-256 --server letsencrypt --cert-profile shortlived</code></pre><p></p><p>After the certificate was issued, I got my notification from Certificate Transparency monitoring via <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>, and I could see the full details of the certificate. Here they are:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image.png" class="kg-image" alt="Blink and you'll miss them: 6-day certificates are here!" loading="lazy" width="870" height="555" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image.png 870w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Just look at that validity period!!</p><p><strong>Valid From</strong>: 15 Nov 2025<br><strong>Valid To</strong>: 22 Nov 2025</p><p>You can find the full details on the <code>shortlived</code> certificate profile from Let's Encrypt, and other supported profiles, on <a href="https://letsencrypt.org/docs/profiles/?ref=scotthelme.ghost.io#shortlived" rel="noreferrer">this page</a>. </p><p></p><h4 id="its-not-just-lets-encrypt">It's not just Let's Encrypt</h4><p>The good news is that Let's Encrypt isn't the only place that you can get your 6-day certificates from either! <a href="https://scotthelme.co.uk/another-free-ca-to-use-via-acme/?ref=scotthelme.co.uk" rel="noreferrer">Google Trust Services</a> also allows you to obtain short-lived certificates, and they have a little more flexibility in that you can request a specific number of days too. Maybe you want 6 days, 12 days, 33 days... Just specify your desired validity period in the request with your ACME client:</p><pre><code>acme.sh --issue --dns dns_cf -d six-days.scotthelme.co.uk --keylength ec-256 --server https://dv.acme-v02.api.pki.goog/directory --extended-key-usage serverAuth --valid-to '+6d'</code></pre><p></p><p>That's another source of 6-day certificates for you, but it did get me wondering.</p><p></p><h4 id="how-low-can-you-go">How low can you go?..</h4><p>Well, fellow certificate nerds, you read my mind!</p><p>The Let's Encrypt <code>shortlived</code> profile doesn't allow for configurable validity periods, none of their profiles do, but GTS does allow for configuration of the validity period... 😎</p><pre><code>acme.sh --issue --dns dns_cf -d one.scotthelme.co.uk --keylength ec-256 --server https://dv.acme-v02.api.pki.goog/directory --extended-key-usage serverAuth --valid-to '+1d'</code></pre><p></p><p>Yes, that command does work, and yes you do get a <strong>ONE-DAY CERTIFICATE</strong>!!</p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-1.png" class="kg-image" alt="Blink and you'll miss them: 6-day certificates are here!" loading="lazy" width="936" height="608" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-1.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-1.png 936w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Just to prove that this really is a thing, here's the PEM encoded certificate!</p><pre><code>-----BEGIN CERTIFICATE----- MIIDdTCCAl2gAwIBAgIQAzk1h8YknV4TAJJazYXsrjANBgkqhkiG9w0BAQsFADA7 MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww CgYDVQQDEwNXUjEwHhcNMjUxMjE3MjEzNjE1WhcNMjUxMjE4MjIzNjExWjAfMR0w GwYDVQQDExRvbmUuc2NvdHRoZWxtZS5jby51azBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABN77LaCQqPHQ1Qx4CsEyiEVARRV5WP+qH9ZyLfO9GzJ+tLfDxROHvYPL YNaCgEiGBbkTOOPOX9qXJPz/g/2AQRejggFaMIIBVjAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUPnLq WpoXyBO+jzNGlH1qUr6SYHkwHwYDVR0jBBgwFoAUZmlJ1N4qnJEDz4kOJLgOMANu iC4wXgYIKwYBBQUHAQEEUjBQMCcGCCsGAQUFBzABhhtodHRwOi8vby5wa2kuZ29v Zy9zL3dyMS9BemswJQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMS5j cnQwHwYDVR0RBBgwFoIUb25lLnNjb3R0aGVsbWUuY28udWswEwYDVR0gBAwwCjAI BgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3Ix L0FwMDR2SjA1Q3lBLmNybDATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0B AQsFAAOCAQEAMsMT7AsLtQqzm0FSsDBq33M9/FAz+Su86NQurk8MXXrSjUrdSKhh zTv2whJcC0W3aPhoqMeeqpsLYQ4AiLgBS2LPoJz2HuFsIfOddrpI3lOHXssT2Wpc MjofbwEOfkDk+jV/rqbz1q+cjbM2VGfoxILgcA7KxVZX0ylvZf52c2zpA9v+sXKu pPFKHHDX2UNSfsPODmWLVWdfFk/ZFbr09urei8ZgdsJhRKABD9BW3aV8QP2dMISh OH6fWcJXrp/w1NjJqIKidMiMgaCe5TDb+j5gOJ+ZLVcKA4WdLtcVYpQIXiT8mIeO rCYNVSkFN4ZIONedfENemM5GgBqcqbpxMA== -----END CERTIFICATE-----</code></pre><p></p><h4 id="automation-is-king">Automation is King</h4><p>The great thing about this, and I've been using these certs for weeks now, is that once you're using an ACME client, you're already automated, and once you're automated, the validity period really isn't relevant any more. I'm currently sticking with the 6-day certs, and I will alternate between Let's Encrypt and Google Trust Services, but running these automations more frequently to go from 90 days down to 6 days really doesn't change anything at all, so give it a try!</p><p></p>]]></content:encoded></item><item><title><![CDATA[What a Year of Solar and Batteries Really Saved Us in 2025]]></title><description><![CDATA[<p>Throughout 2025, I spoke a few times about our home energy solution, including our grid usage, our solar array and our Tesla Powerwall batteries. Now that I have a full year of data, I wanted to take a look at exactly how everything is working out, and, in alignment with</p>]]></description><link>https://scotthelme.ghost.io/what-a-year-of-solar-and-batteries-really-saved-us-in-2025/</link><guid isPermaLink="false">6962376aa2de7d0001fa2a36</guid><category><![CDATA[Tesla Powerwall]]></category><category><![CDATA[Solar Power]]></category><category><![CDATA[Octopus Energy]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Tue, 13 Jan 2026 11:24:31 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp" alt="What a Year of Solar and Batteries Really Saved Us in 2025"><p>Throughout 2025, I spoke a few times about our home energy solution, including our grid usage, our solar array and our Tesla Powerwall batteries. Now that I have a full year of data, I wanted to take a look at exactly how everything is working out, and, in alignment with our objectives, how much money we've saved!</p><p></p><h4 id="our-setup">Our setup</h4><p>Just to give a quick overview of what we're working with, here are the details on our solar, battery and tariff situation:</p><ul><li>☀️Solar Panels: We have 14x Perlight solar panels managed by Enphase that make up the 4.2kWp array on our roof, and they produce energy when the sun shines, which isn't as often as I'd like in the UK!</li><li>🔋Tesla Powerwalls: We have 3x Tesla Powerwall 2 in our garage that were purchased to help us load-shift our energy usage. Electricity is very expensive in the UK and moving from peak usage which is 05:30 to 23:30 at ~£0.28/kWh, to off-peak usage, which is 23:30 - 05:30 at ~£0.07/kWh, is a significant cost saving.</li><li>💡Smart Tariff: My wife and I both drive electric cars and our electricity provider, Octopus Energy, has a Smart Charging tariff. If we plug in one of our cars, and cheap electricity is available, they will activate the charger and allow us to use the off-peak rate, even at peak times.</li></ul><p></p><p>Now that we have some basic info, let's get into the details!</p><p></p><h4 id="grid-import">Grid Import</h4><p>I have 3 sources of data for our grid import, and all of them align pretty well in terms of their measurements. I have the amount our electricity supplier charged us for, I have my own CT Clamp going via a Shelly EM that feeds in to Home Assistant, and I have the Tesla Gateway which controls all grid import into our home.</p><p>Starting with my Home Assistant data, these are the relevant readings. </p><p>Jan 1st 2025 - 15,106.10 kWh<br>Dec 31st 2025 - 36,680.90 kWh<br>Total: 21,574.80 kWh<br><strong>Total Import: 21.6 MWh</strong></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-3.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1138" height="503" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-3.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-3.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-3.png 1138w" sizes="(min-width: 720px) 720px"></figure><p></p><p>As you can see in the graph, during the summer months we have slightly lower grid usage and the graph line climbs at a lower rate, but overall, we have pretty consistent usage. Looking at what our energy supplier charged, us for, that comes in slightly lower.</p><p><strong>Total Import: 20.1 MWh</strong></p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-8.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="997" height="577" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-8.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-8.png 997w" sizes="(min-width: 720px) 720px"></figure><p></p><p>I'm going to use the figure provided by our energy supplier in my calculations because their equipment is likely more accurate than mine, and also, what they're charging me is the ultimate thing that matters. The final source is our Tesla Gateway, which shows us having imported 21.0 MWh.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-11.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1290" height="1568" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-11.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-11.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-11.png 1290w" sizes="(min-width: 720px) 720px"></figure><p></p><p>It's great to see how all of these sources of data align so poorly! 😅</p><h4 id="grid-export">Grid Export</h4><p>Looking at our export, the graph tells a slightly different story because, as you can see, we didn't really start exporting properly until June, when our export tariff was activated. Prior to June, it simply wasn't worth exporting as we were only getting £0.04/kWh but at the end of May, our export tariff went live and we were then getting paid £0.15/kWh for export. My <a href="https://scotthelme.co.uk/automation-improvements-after-a-tesla-powerwall-outage/?ref=scotthelme.ghost.io" rel="noreferrer">first</a> and <a href="https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">second</a> blog posts cover the full details of this change when it happened if you'd like to read them but for now, just note that it will change the calculations a little later as we only had export for 60% of the year.</p><p><strong>Total Export: 6.0 MWh</strong></p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-9.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="989" height="582" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-9.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-9.png 989w" sizes="(min-width: 720px) 720px"></figure><p></p><p>With our grid export covered the final piece of the puzzle is to look at our solar.</p><p></p><h4 id="solar-production">Solar Production</h4><p>We're really not in the best part of the world for generating solar power, but we've still managed to produce quite a bit of power. Even in the most ideal, perfect scenario, our solar array can only generate 4.2kW of power, and we're definitely never getting near that. Our peak production was 2.841kW on 8th July at 13:00, and you can see our full annual production graph here. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-12.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1582" height="513" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-12.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-12.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-12.png 1582w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Looking at the total energy production for the entire array, you can see it pick up through the sunnier months but remain quite flat during the darker days of the year.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-2.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1132" height="504" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-2.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-2.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-2.png 1132w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Jan 1st 2025 - 2.709 MWh<br>Dec 31st 2025 - 5.874 MWh<br><strong>Solar Production: 3.2 MWh</strong></p><p></p><p>Just to confirm, I also took a look at the Enphase app, which is drawing it's data from the same source to be fair, and it agrees with the 3.2 MWh of generation.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-4.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="1157" height="560" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-4.png 1157w" sizes="(min-width: 720px) 720px"></figure><p></p><h4 id="calculating-the-savings">Calculating the savings</h4><p>This isn't exactly straightforward because of the combination of our solar array and excess import/export due to the batteries, but here are the numbers I'm currently working on.</p><p><strong>Total Import: 20.1 MWh<br>Total Export: 6.0 MWh<br>Solar Production: 3.2 MWh</strong></p><p></p><p>That gives us a total household usage of 17.3 MWh.</p><p>(20.1 MWh import + 3.2 MWh solar) − 6.0 MWh export = 17.3 MWh usage</p><p>If we didn't have the solar array providing power, the full 17.3 MWh of consumption would have been chargeable from our provider. If we had only the solar and no battery, assuming a perfect ability to utilise our solar generation, only 14.1 MWh of our usage would need to be imported. The cost of those units of solar generation can be viewed at the peak and off-peak rates as follows.</p><p>Peak rate: 3,200 kWh x £0.28/kWh = £896<br>Off-peak rate: 3,200 kWh x £0.07/kWh = £224</p><p>Given that solar panels only produce during peak electricity rates, it would be reasonable to use the higher price here. A consideration for us though is that we do have batteries, and we're able to load-shift all of our usage into the off-peak rate, so arguably the solar panels only made £224 of electricity. </p><p>The bigger savings come when we start to look at the cost of the grid import. Assuming we had no solar panels, we'd have imported 17.3 MWh of electricity, and with the solar panels and perfect utilisation, we'd have imported 14.1 MWh of electricity. That's quite a lot of electricity and calculating the different costs of peak vs. off-peak by using batteries to load shift our usage gives some quite impressive results.</p><p>Peak rate: 17,300 kWh x £0.28/kWh = £4,844<br>Peak rate with solar: 14,100 kWh x £0.28 = £3,948</p><p>Off-peak rate: 17,300 kWh x £0.07/kWh = £1,211<br>Off-peak rate with solar: 14,100 kWh x £0.07/kWh = £987</p><p>This means there's a potential swing from £4,844 down to £987 with solar and battery, a total potential saving of £3,857!</p><p>This also tracks if we look at our monthly spend on electricity which went from £350-£400 per month down to £50-£100 per month depending on the time of year. But it gets better.</p><p></p><h4 id="exporting-excess-energy">Exporting excess energy</h4><p>Our solar array generates almost nothing in the winter months so our batteries are sized to allow for a full day of usage with basically no solar support. We can go from the start of the peak rate at 05:30 all the way to the off-peak rate at 23:30 without using any grid power. When it comes to the summer months, though, our solar array is producing a lot of power and we clearly have a capability to export a lot more. The batteries can fill up on the off-peak rate overnight at £0.07/kWh, and then export it during the peak rate for £0.15/kWh, meaning any excess solar production or battery capacity can be exported for a reasonable amount.</p><p>If we take a look at the billing information from our energy supplier, we can see that during July, our best month for solar production, we exported a lot of energy. We exported so much energy that it actually fully offset our electricity costs and allowed us to go negative, meaning we were earning money back.</p><p>Here is our electricity import data:</p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-7.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="988" height="623" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-7.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-7.png 988w" sizes="(min-width: 720px) 720px"></figure><p></p><p>And here is our electricity export data:</p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2026/01/image-6.png" class="kg-image" alt="What a Year of Solar and Batteries Really Saved Us in 2025" loading="lazy" width="983" height="706" srcset="https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-6.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-6.png 983w" sizes="(min-width: 720px) 720px"></figure><p></p><p>That's a pretty epic scenario, despite us being such high energy consumers, to still have the ability to fully cover our costs and even earn something back! For clarity, we will still have the standing charge component of our bill, which is £0.45/day so about £13.50 per month to go on any given month, but looking at the raw energy costs, it's impressive.</p><p></p><h4 id="the-final-calculation">The final calculation</h4><p>I pulled all of our charges for electricity in 2025 to see just how close my calculations were and to double check everything I was thinking. Earlier, I gave these figures:</p><p>Off-peak rate: 17,300 kWh x £0.07/kWh = £1,211</p><p>If 100% of our electricity usage was at the off-peak rate, we should have paid £1,211 for the year. Adding up all of our monthly charges, our total for the year was £1,608.11 all in, but we need to subtract our standing charge from that.</p><p>Total cost = £1,608.11 - (365 * £0.45)<br><strong>Total import = £1,443.86</strong></p><p></p><p>This means that we got almost all of our usage at the off-peak rate which is an awesome achievement! After the charges for electricity, I then tallied up all of our payments for export.</p><p><strong>Total export = £886.49</strong></p><p></p><p>Another pretty impressive achievement, earning so much in export, which also helps to bring our net electricity cost in 2025 to <strong>£557.37</strong>! To put this another way, the effective rate of our electricity is now just £0.03/kWh.</p><p>£557.37 / 17,300kWh = <strong>£0.03/kWh</strong></p><p></p><h4 id="but-was-it-all-worth-it">But was it all worth it?</h4><p>That's a tricky question to answer, and everyone will have different objectives and desired outcomes, but ours was pretty clear. Running two Electric Vehicles, having two adults working from home full time, me having servers and equipment at home, along with a power hungry hot tub, we were spending too much per month in electricity alone, and our goal was to reduce that.</p><p>Of course, it only makes sense to spend money reducing our costs if we reduce them enough to pay back the investment in the long term, and things are looking good so far. Here are the costs for our installations:</p><p></p><p>£17,580 - Powerwalls #1 and #2 installed.<br>£13,940 - Solar array installed.<br>£7,840 - Powerwall #3 installed.<br>Total cost = £39,360</p><p></p><p>If we assume even a generous 2/3 - 1/3 split between peak and off-peak usage, with no Powerwalls or solar array, our electricity costs for 2025 would have been £3,632.86:</p><p>11,533 kWh x £0.28/kWh = £3,229.24<br>5,766 kWh x £0.07/kWh = £403.62<br>Total = £3,632.86</p><p></p><p>Instead, our costs were only £557.37, meaning we saved £3,078.49 this year. We also only had export capabilities for 7 months of 2025, so in 2026 when we will have 12 months of export capabilities, we should further reduce our costs. I anticipate that in 2026 our electricity costs for the year will be ~£0, and that's our goal.</p><p>Having our full costs returned in ~11 years is definitely something we're happy with, and we've also had protection against several power outages in our area along the way, which is a very nice bonus. Another way to look at this is that the investment is returning ~9%/year.</p><p></p><table> <thead> <tr> <th style="text-align:right">Year</th> <th style="text-align:right">Cumulative savings (£)</th> <th style="text-align:right">ROI (%)</th> </tr> </thead> <tbody> <tr> <td style="text-align:right">1</td> <td style="text-align:right">3,632.86</td> <td style="text-align:right">9.23%</td> </tr> <tr> <td style="text-align:right">2</td> <td style="text-align:right">7,265.72</td> <td style="text-align:right">18.46%</td> </tr> <tr> <td style="text-align:right">3</td> <td style="text-align:right">10,898.58</td> <td style="text-align:right">27.69%</td> </tr> <tr> <td style="text-align:right">4</td> <td style="text-align:right">14,531.44</td> <td style="text-align:right">36.92%</td> </tr> <tr> <td style="text-align:right">5</td> <td style="text-align:right">18,164.30</td> <td style="text-align:right">46.15%</td> </tr> <tr> <td style="text-align:right">6</td> <td style="text-align:right">21,797.16</td> <td style="text-align:right">55.38%</td> </tr> <tr> <td style="text-align:right">7</td> <td style="text-align:right">25,430.02</td> <td style="text-align:right">64.61%</td> </tr> <tr> <td style="text-align:right">8</td> <td style="text-align:right">29,062.88</td> <td style="text-align:right">73.84%</td> </tr> <tr> <td style="text-align:right">9</td> <td style="text-align:right">32,695.74</td> <td style="text-align:right">83.07%</td> </tr> <tr> <td style="text-align:right">10</td> <td style="text-align:right">36,328.60</td> <td style="text-align:right">92.30%</td> </tr> <tr> <td style="text-align:right">15</td> <td style="text-align:right">54,492.90</td> <td style="text-align:right">138.43%</td> </tr> <tr> <td style="text-align:right">20</td> <td style="text-align:right">72,657.20</td> <td style="text-align:right">184.61%</td> </tr> <tr> <td style="text-align:right">25</td> <td style="text-align:right">90,821.50</td> <td style="text-align:right">230.76%</td> </tr> </tbody> </table> <p> </p><p>Of course, at some point during that period, the effective value of the installation will reduce to almost £0, and we have to consider that, but it's doing pretty darn good. If we hadn't needed to add that third Powerwall, this would have been so much better too. We'll see what the future holds, but with the inevitable and continued rise of energy costs, and talk of moving the standing charge on to our unit rate, things might look even better in the future.</p><p></p><h4 id="onwards-to-2026">Onwards to 2026!</h4><p>Now that we have everything properly set up, and I'm happy with all of our Home Assistant automations, we're going to see how 2026 goes. I will definitely circle back in a year from now and see how the numbers played out, and until then, I hope the information here has been useful or interesting 👍</p><p></p>]]></content:encoded></item><item><title><![CDATA[Report URI Penetration Test 2025]]></title><description><![CDATA[<p>Every year, just as we start to put up the Christmas Tree, we have another tradition at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a> which is to conduct our annual penetration test! </p><p>🎅🎄🎁 --> 🩻🔐🥷</p><p>This will be our 6th annual penetration test that we've posted completely publicly,</p>]]></description><link>https://scotthelme.ghost.io/report-uri-penetration-test-2025/</link><guid isPermaLink="false">693822108d605500017a6622</guid><category><![CDATA[Report URI]]></category><category><![CDATA[Penetration Test]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 15 Dec 2025 15:36:37 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp" alt="Report URI Penetration Test 2025"><p>Every year, just as we start to put up the Christmas Tree, we have another tradition at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a> which is to conduct our annual penetration test! </p><p>🎅🎄🎁 --> 🩻🔐🥷</p><p>This will be our 6th annual penetration test that we've posted completely publicly, just as before, and we'll be covering a full run down of what was found and what we've done about it. </p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/12/image.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1915" height="356" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/12/image.png 1600w, https://scotthelme.ghost.io/content/images/2025/12/image.png 1915w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="penetration-tests">Penetration Tests</h4><p>If you find this post interesting or would like to see our previous reports, then here are the links to each and every one of those!</p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2020/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2020</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2021/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2021</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2022/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2022</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2023/?ref=scotthelme.co.uk" rel="noreferrer">Report URI Penetration Test 2023</a></p><p><a href="https://scotthelme.co.uk/report-uri-penetration-test-2024/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI Penetration Test 2024</a></p><p></p><h4 id="the-results">The Results</h4><p>2025 has been another good year for us as we've continued to focus on the security of our product, and the results of the test show that. Not only have we added a bunch of new features, we've also made some significant changes to our infrastructure and made countless changes and improvements to existing functionality too. The tester had what was effectively an unlimited scope to target the application, a full 'guidebook' on how to get up and running with our product and a demo call to ensure all required knowledge was handed over before the test. We wanted them to hit the ground running and waste no time getting stuck in.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-1.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="724" height="409" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-1.png 600w, https://scotthelme.ghost.io/content/images/2025/12/image-1.png 724w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Finding an Info rated issue, three Low rated and a Medium severity issue definitely gives us something to talk about, so let's look at that Medium severity first. </p><p></p><h4 id="csv-formula-injection">CSV Formula Injection</h4><p>The entire purpose of our service is to ingest user-generated data and then display that in some way. Every single telemetry report we process comes from either a browser or a mail server, and the entire content, whilst conforming to a certain schema, is essentially free in terms of the values of fields. We have historically focused on, and thus far prevented, XSS from creeping it's way in, but this bug takes a slightly different form. </p><p>Earlier this year, June 11th to be exact, we released a new feature that allowed for a raw export of telemetry data. This was a commonly requested feature from our customers and we provided two export formats for the data, the native JSON that telemetry is ingested in, or a CSV variant too. You can see the export feature being used here on CSP Reports.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-2.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1553" height="543" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-2.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-2.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-2.png 1553w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Because the data export is a raw export, we are providing the telemetry payloads that we received from the browser or email server, just as you would have received them if you collected them yourself. It turns out that as these fields obviously contain user generated data, and you can do some trickery!</p><p>The specific example given by the tester uses a NEL report, but it's not a problem specific to NEL reports, it's possible across all of our telemetry. Looking at the example in the report, you can see how the tester crafted a specific payload:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-3.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1005" height="671" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-3.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-3.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-3.png 1005w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This <code>type</code> value contains an Excel command that will make it through to the CSV export as it represents the raw value sent by the client. The steps to leverage this are pretty convoluted, but I will quickly summarise them here by using an example if you want to target my good friend <a href="https://troyhunt.com/?ref=scotthelme.ghost.io" rel="noreferrer">Troy Hunt</a>, who runs <a href="https://haveibeenpwned.com/?ref=scotthelme.ghost.io" rel="noreferrer">Have I Been Pwned</a> which indeed uses Report URI.</p><ol><li>Identify the subdomain that Troy uses on our service, which is public information. </li><li>Send telemetry events to that endpoint with specifically crafted payloads which will be processed in to Troy's account.</li><li>Troy would then need to view that data in our UI, and for any reason wish to export that data as a CSV file. </li><li>Then, Troy would have to open that CSV file specifically in Excel, and bypass the two security warnings presented when opening the file. </li></ol><p></p><p>There are a couple of points in there that require some very specific actions from Troy, and the chances of all of those things happening are a little far-fetched, but still, we gave it a lot of consideration. The problem I had is that the export is raw, it's meant to be an export of what the browser or email server provided to us so you have a verbatim copy of the raw data. Sanitising that data is also tricky as there isn't a universal way to sanitise CSV because it depends on what application you're going to open it with, something I discovered when testing out our fix!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-4.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1140" height="582" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-4.png 1140w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Our current approach is to add a single quote to the start of the value if we detect that the first non-whitespace character is a potentially dangerous character, and that seems to have reliably solved the issue. I'm happy to hear feedback on this approach, or alternative suggestions, so drop by the comments below if you can contribute!</p><p></p><h4 id="vulnerabilities-in-outdated-dependencies">Vulnerabilities in Outdated Dependencies </h4><p>Not again! Actually, I'm pretty happy with the finding here, but I will explain both of the issues raised and what we did and didn't do about them. </p><h6 id="bootstrap-v3x">Bootstrap v3.x</h6><p>The last version of Bootstrap 3 was v3.4.1 which did have an XSS vulnerability present (<a href="https://security.snyk.io/package/npm/bootstrap/3.4.1?ref=scotthelme.ghost.io" rel="noreferrer">source</a>). We have since cloned v3.4.1 and patched it up to v3.4.4 ourselves to fix various issues that have been found, including the XSS issue raised here. The issue is still flagged in v3.x though, which is why it was flagged in our custom patched version, so in reality, there is no problem here and we're happy to keep using our own version. </p><h6 id="jquery-cookie-v141">jQuery Cookie v1.4.1</h6><p>Another tricky one because the Snyk data that we refer to lists no vulnerability in this library (<a href="https://security.snyk.io/package/npm/jquery.cookie/1.4.1?ref=scotthelme.ghost.io" rel="noreferrer">source</a>) which is why our own tooling hasn't flagged this to us. That said, the NVD does list a CVE for this version of the jQuery Cookie plugin (<a href="https://nvd.nist.gov/vuln/detail/cve-2022-23395?ref=scotthelme.ghost.io" rel="noreferrer">source</a>) but I can't find other data to back that up, including their own link to Snyk which doesn't list a vulnerability. Rather than spend too much time on this for what is a relatively simple plugin, we decided to remove it and implement the functionality that we need ourselves, solving the problem.</p><p></p><p>With both of those issues addressed, I'm happy to say that we can consider this as resolved too.</p><p></p><h4 id="insufficient-session-expiration">Insufficient Session Expiration</h4><p>This issue has been raised previously in our <a href="https://scotthelme.co.uk/report-uri-penetration-test-2021/?ref=scotthelme.co.uk" rel="noreferrer">2021 penetration test</a>, and our position remains similar to what it was back then. Whilst I'm leaning towards 24 hours being on the top end, and we will probably bring this down shortly, I also feel like the recommended 20 minutes is just too short. Going away from your desk for a coffee break and returning to find you've been logged out just seems a little bit too aggressive for us.</p><p>Looking at the other suggested concerns, if you have malware running on your endpoint, or someone gains physical access to extract a session cookie, I feel like you probably have much bigger concerns to address too! Overall, I acknowledge the issue raised and we're currently thinking something in the 12-18 hours range might be better suited. </p><p></p><h4 id="insecure-tls-configuration">Insecure TLS Configuration </h4><p>You could argue that I know a thing or two about TLS configuration, heck, you can even attend my <a href="https://www.feistyduck.com/training/practical-tls-and-pki?ref=scotthelme.ghost.io" rel="noreferrer">training course</a> on it if you like! The issue that somebody like a pen tester coming in from the outside is that they're always going to lack the specific context on why we made the configuration choices we did, and I also recognise that it's very hard to give generic advice that fits all situations. </p><p>We have priority in our cipher suite list for all of the best cipher suites as some of the first choices, and we have support for the latest protocol versions too. We're doing so well in fact that we get an A grade on the SSL Labs test (<a href="https://www.ssllabs.com/ssltest/analyze.html?d=helios.report-uri.com&ref=scotthelme.ghost.io" rel="noreferrer">results</a>):</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/12/image-5.png" class="kg-image" alt="Report URI Penetration Test 2025" loading="lazy" width="1284" height="671" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-5.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-5.png 1284w" sizes="(min-width: 720px) 720px"></figure><p></p><p>I'm happy with where our current configuration is but as always, we will keep it in constant review as time goes by!</p><p></p><h4 id="no-account-lockout-or-timeout-mechanism">No Account Lockout or Timeout Mechanism </h4><p>This is a controversial topic and it's quite interesting to see it come up because we specifically cover this in the <a href="https://www.troyhunt.com/workshops/?ref=scotthelme.co.uk" rel="noreferrer">Hack Yourself First</a> workshop that I deliver alongside Troy Hunt! I absolutely recognise the goal that a mechanism like this would be trying to achieve, but I worry about the potential negative side-effects.</p><p>Using account enumeration it's often trivial to determine if someone has an account on our service, so you might be able to determine that [email protected] is indeed registered. You then may want to start guessing different passwords to try and log in to Troy's account, and there lies the problem. How many times should you be able to sit there and guess a password before something happens? An account lockout mechanism might work along the lines of saying 'after 5 unsuccessful login attempts we will lock the account and require a password reset', or perhaps say 'after 5 unsuccessful login attempts you will not be able to login again for 3 minutes'. Both of these would stop the attacker from making significant progress, but both of them also present an opportunity to be abused by an attacker too. The attacker can now sit and make repeated login attempts to Troy's account and keep it in a perpetually locked state, denying him the use of his account, a Denial-of-Service attack (DoS)! It's for this reason I'm generally not fond of these account lockout / account suspension mechanisms, they do provide an opportunity for abuse. </p><p>Instead, we rely on a different set of protections to try and limit the impact of attacks like these. </p><ol><li>We implement incredibly strict rate-liming on sensitive endpoints, including our authentication endpoints. This would slow an attacker down so the rate at which they could make guesses would be reduced. (This was disabled for the client IP addresses used by the tester)</li><li>We utilise Cloudflare's Bot Management across our application, which includes authentication flows, and if there is any reasonable suspicion that the client is a bot or in some way automated, they would be challenged. This prevents attackers from automating their attacks, ultimately slowing them down. (This was disabled for the client IP addresses used by the tester)</li><li>We have taken exceptional measures around password security. We require strong and complex passwords for our service, check for commonly used passwords against the Pwned Passwords API, use zxcvbn for strength testing, and more. This makes it highly unlikely that the password being guessed could be guessed easily, and you can read the full details on our password security measures <a href="https://scotthelme.co.uk/boosting-account-security-pwned-passwords-and-zxcvbn/?ref=scotthelme.ghost.io" rel="noreferrer">here</a>.</li><li>We support, and have a very high adoption of, 2FA across our service. This means that there's a very good chance that if the attacker was able to guess a password, the next prompt would be to input the TOTP code from the authenticator app!</li></ol><p>Given the above concerns around lockout mechanisms, and the additional measures we have in place, I continue to remain happy with our current position but we will always review these things on an ongoing basis. </p><p></p><h4 id="thats-a-wrap">That's a wrap!</h4><p>Given how much continued development we see in the product, and how much our infrastructure is evolving over time, I'm really pleased to see that our continued efforts to maintain the security of our product, and ultimately our customer's data, has paid off. </p><p>As we look forward to 2026 our "Development Horizon" project board is loaded with cool new features and updates, so be sure to keep an eye out for the exciting new things we have coming!</p><p>If you want to download a copy of our report, the latest report is always available and linked in the <a href="https://report-uri.com/?ref=scotthelme.ghost.io#footer" rel="noreferrer">footer of our site</a>!</p><p></p>]]></content:encoded></item><item><title><![CDATA[Report URI - outage update]]></title><description><![CDATA[<p>This is not a blog post that anybody ever wants to write, but we had some service issues yesterday and now the dust has settled, I wanted to provide an update on what happened. The good news is that the interruption was very minor in the end, and likely went</p>]]></description><link>https://scotthelme.ghost.io/report-uri-outage-update/</link><guid isPermaLink="false">691dee03d9c78000011bd122</guid><category><![CDATA[Report URI]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Wed, 19 Nov 2025 21:24:19 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp" alt="Report URI - outage update"><p>This is not a blog post that anybody ever wants to write, but we had some service issues yesterday and now the dust has settled, I wanted to provide an update on what happened. The good news is that the interruption was very minor in the end, and likely went unnoticed by most of our customers. </p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide-1.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="974" height="141" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide-1.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide-1.png 974w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="what-happened">What happened?</h4><p>I'm sure that many of you are already aware of the issues that Cloudflare experienced yesterday, and their post-mortem is now available <a href="https://blog.cloudflare.com/18-november-2025-outage/?ref=scotthelme.ghost.io" rel="noreferrer">on their blog</a>. It's always tough to have service issues, but as expected Cloudflare handled it well and were transparent throughout. As a customer of Cloudflare that uses many of their services, the Cloudflare outage unfortunately had an impact on our service too. Because of the unique way that our service operates, <strong><em>our subsequent service issues did not have any impact on the websites or operations of our customers</em></strong>. What we do have to recognise, though, is that we may have missed some telemetry events for a short period of time.</p><p></p><h4 id="our-infrastructure">Our infrastructure</h4><p>Because all of the telemetry events sent to us have to pass through Cloudflare first, when Cloudflare were experiencing their service issues, it did prevent telemetry from reaching our servers. If we take a look at the bandwidth for one of our many telemetry ingestion servers, we can clearly see the impact.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/11/ingestion-server.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="991" height="280" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/ingestion-server.png 600w, https://scotthelme.ghost.io/content/images/2025/11/ingestion-server.png 991w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Looking at the graph from the Cloudflare blog showing their 500 error levels, we have a near perfect alignment with us not receiving telemetry during their peak error rates.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/11/image.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="1507" height="733" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/11/image.png 1000w, https://scotthelme.ghost.io/content/images/2025/11/image.png 1507w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">source: Cloudflare</span></figcaption></figure><p></p><p>The good news here, as mentioned above, is that even if a browser can't reach our service and send the telemetry to us, it has no negative impact on our customer's websites, at all, as the browser will simply continue to load the page and try to send the telemetry again later. This is a truly unique scenario where we can have a near total service outage and it's unlikely that a single customer even noticed because we have no negative impact on their application.</p><p></p><h4 id="the-recovery">The recovery</h4><p>Cloudflare worked quickly to bring their service troubles under control and things started to return to normal for us around 14:30 UTC. We could see our ingestion servers start to receive telemetry again, and we started to receive much more than usual. Here's that same view for the server above.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/11/ingestion-server-2.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="997" height="285" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/ingestion-server-2.png 600w, https://scotthelme.ghost.io/content/images/2025/11/ingestion-server-2.png 997w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If we take a look at the aggregate inbound telemetry for our whole service, we were comfortably receiving twice our usual volume of telemetry data.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/11/global-telemetry.png" class="kg-image" alt="Report URI - outage update" loading="lazy" width="994" height="282" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/global-telemetry.png 600w, https://scotthelme.ghost.io/content/images/2025/11/global-telemetry.png 994w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This is a good thing and shows that the browsers that had previously tried to dispatch telemetry to us and had failed were now retrying and succeeding. We did keep a close eye on the impact that this level of load was having, and we managed it well, with the load tailing off to our normal levels overnight. Whilst this recovery was really good to see, we have to acknowledge that there will inevitably be telemetry that was dropped during this time, and it's difficult to accurately gauge how much. If the telemetry event was retried successfully by the browser, or the problem also existed either before or after this outage, we will have still processed the event and taken any necessary action. </p><p></p><h4 id="looking-forwards">Looking forwards</h4><p>I've always talked openly about our infrastructure at Report URI, even blogging in detail about the issues we've faced and the changes we've made as a result, much as I am doing here. We depend on several other service providers to build our service, including Cloudflare for CDN/WAF, DigitalOcean for VPS/compute and Microsoft Azure for storage, but sometimes even the big players will have their own problems, just like AWS did recently too. </p><p>Looking back on this incident now, whilst it was a difficult process for us to go through, I believe we're still making the best choices for Report URI and our customers. The likelihood of us being able to build our own service that rivals the benefits that Cloudflare provides is zero, and looking at other service providers to migrate to seems like a knee-jerk overreaction. I'm not looking for service providers that promise to never have issues, I'm looking for service providers that will respond quickly and transparently when they inevitably do have an issue, and Cloudflare have demonstrated that again. It's also this same desire for transparency and honesty that has driven me to write this blog post to inform you that it is likely we missed some of your telemetry events yesterday, and that we continue to consider how we can improve our service further going forwards.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Integrity Policy - Monitoring and Enforcing the use of SRI]]></title><description><![CDATA[<p>This has been a long time coming so I'm excited that we now have a working standard in the browser for monitoring and enforcing the use of SRI across your website assets!</p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png" class="kg-image" alt loading="lazy" width="974" height="141" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png 974w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="sri-refresher">SRI refresher</h4><p>For those that aren't familiar, or would like a quick refresher, here&</p>]]></description><link>https://scotthelme.ghost.io/integrity-policy-monitoring-and-enforcing-the-use-of-sri/</link><guid isPermaLink="false">691b2097bb6a68000181de36</guid><category><![CDATA[Report URI]]></category><category><![CDATA[Integrity Policy]]></category><category><![CDATA[javascript]]></category><category><![CDATA[integrity]]></category><category><![CDATA[Integrity Suite]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Wed, 19 Nov 2025 15:01:02 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp" alt="Integrity Policy - Monitoring and Enforcing the use of SRI"><p>This has been a long time coming so I'm excited that we now have a working standard in the browser for monitoring and enforcing the use of SRI across your website assets!</p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png" class="kg-image" alt="Integrity Policy - Monitoring and Enforcing the use of SRI" loading="lazy" width="974" height="141" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png 974w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="sri-refresher">SRI refresher</h4><p>For those that aren't familiar, or would like a quick refresher, here's the TLDR of SRI - Subresource Integrity. When loading assets from a 3rd-party, especially JavaScript, it's a good idea to have some control over what exactly it is you're loading. A typical script tag will allow <em>any</em> script to load in your page, but SRI gives you the ability to make sure the JavaScript you're getting is the JavaScript you wanted... It's all done with the simple addition of an integrity attribute:</p><p></p><pre><code><script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"> </script></code></pre><p></p><p>That integrity attribute means the browser can now download that file and check the cryptographic fingerprint of the file it downloaded matches the one that you were expecting! If the file is tampered with or modified in some way, the browser can reject it. I have a full blog post about SRI, <a href="https://scotthelme.co.uk/subresource-integrity/?ref=scotthelme.ghost.io" rel="noreferrer">Subresource Integrity: Securing CDN loaded assets</a>, that I wrote <strong><em>more than a decade ago</em></strong> back in 2015!</p><p></p><h4 id="the-rise-of-sri">The rise of SRI</h4><p>It's no surprise that SRI has become a very popular technology. It's free to use, it's an open web standard supported across all browsers, it's unbelievably simple, and it provides invaluable protection against some quite serious threats. Helping to drive that adoption, all of the major CDN providers have been supplying their script tags with SRI attributes for years, it could have stopped some pretty serious attacks in recent history [<a href="https://scotthelme.co.uk/protect-site-from-cryptojacking-csp-sri/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>], and it's now even a recommended strategy in compliance standards like PCI DSS [<a href="https://scotthelme.co.uk/pci-dss-4-0-its-time-to-get-serious-on-magecart/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]. If you have existing assets that don't have SRI protection, and you'd like to add it, you can use free tools like our <a href="https://report-uri.com/home/sri_hash?ref=scotthelme.ghost.io" rel="noreferrer">SRI Hash Generator</a> to take the URL of existing assets and create an SRI compatible script or style tag. But there lies a little bit of a problem. How do you know what assets that you have across your website that are eligible to use SRI, but aren't currently using it? </p><p></p><h4 id="integrity-policy-to-the-rescue">Integrity Policy to the rescue!</h4><p>We now have a way to effortlessly audit all of the dependencies across your application to ensure that they're using SRI. Integrity Policy is an open web standard, requires no code or agent to be deployed, and has no negative impact to speak of. It can be enabled with a single HTTP Response Header:</p><p></p><pre><code>Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(default)</code></pre><p></p><p>As you can see here, I'm using the <code>Report-Only</code> version of the header as it's best to start by gathering information before you consider any enforcing action. I'm setting the policy to monitor JavaScript and I'm instructing it to send telemetry to the <code>default</code> reporting endpoint. If you're not familiar with the <a href="https://scotthelme.co.uk/introducing-the-reporting-api-nel-other-major-changes-to-report-uri/?ref=scotthelme.ghost.io" rel="noreferrer">Reporting API</a> you can read my full blog post, but the summary is that you simply add a HTTP Response Header to let the browser know where to send the telemetry. This endpoint can also be used by a variety of different mechanisms so you can set it once and use it many times.</p><p></p><pre><code>Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://helios.report-uri.com/a/t/g"}],"include_subdomains":true}</code></pre><p></p><p>That's it! With both of those headers set, any time a browser loads one of your pages it will send an event to let you know if there is an asset being loaded without the use of SRI. As the Integrity Policy header was delivered in Report-Only mode, it will still allow the asset to load so there is no negative impact on your site. The idea here would be that you can now go and fix that problem by adding the integrity attribute to the asset, and then the events will no longer be sent.</p><p></p><h4 id="now-in-open-beta">Now in open beta!</h4><p>Integrity Policy is now available on our site and is free to access for all customers during the open beta period. After announcing another brand new feature only a couple of months ago, <a href="https://scotthelme.co.uk/capture-javascript-integrity-metadata-using-csp/?ref=scotthelme.ghost.io" rel="noreferrer">CSP Integrity</a>, these two features will form part of our new Integrity Suite offering that will become generally available in Q1 2026.</p><p>There's no doubt that these new capabilities are a fundamental improvement in client-side security, allowing for a level of native protection in the browser that is simply unmatched. Whilst the details we've covered in the CSP Integrity blog post, and now in this Integrity Policy blog post, are exciting, we still have a <strong><em>lot</em></strong> more to announce in the coming months!</p><p></p>]]></content:encoded></item><item><title><![CDATA[CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded]]></title><description><![CDATA[<p>We're very public and open about our infrastructure at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>, having written many blog posts about how we process billions of telemetry events every single week. As a result, it's no secret that we use Redis quite heavily across our infrastructure, and some have asked</p>]]></description><link>https://scotthelme.ghost.io/cve-2025-49844-the-redis-cvss-10-0-vulnerability-and-how-we-responded/</link><guid isPermaLink="false">68e7a6e405c3450001b53f41</guid><category><![CDATA[Report URI]]></category><category><![CDATA[Redis]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Tue, 14 Oct 2025 08:11:47 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp" alt="CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded"><p>We're very public and open about our infrastructure at <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>, having written many blog posts about how we process billions of telemetry events every single week. As a result, it's no secret that we use Redis quite heavily across our infrastructure, and some have asked how the recent security vulnerability discovered in Redis has impacted us.</p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/10/report-uri.png" class="kg-image" alt="CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded" loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/10/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/10/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="tldr">TLDR;</h4><p>The short answer is that <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-49844?ref=scotthelme.ghost.io" rel="noreferrer">CVE-2025-49844</a> hasn't impacted us at all, largely because of our strict stance on security and existing measures that we already had in place. That said, any time something big happens in the industry that might have affected us, we take a look at how we might improve even further.</p><p></p><h4 id="immediate-response">Immediate response</h4><p>This advisory from Redis (<a href="https://github.com/redis/redis/security/advisories/GHSA-4789-qfc9-5f9q?ref=scotthelme.ghost.io" rel="noreferrer">GHSA-4789-qfc9-5f9q</a>) is the first visibility I had into the issue and the specified workaround was to use the ACL feature in Redis to restrict access to the commands that might be abused.</p><p></p><blockquote>An additional workaround to mitigate the problem without patching the redis-server executable is to prevent users from executing Lua scripts. This can be done using ACL to restrict EVAL and EVALSHA commands.</blockquote><p></p><p>This is an easy protection to implement and I know we don't use the <code>EVAL</code> or <code>EVALSHA</code> commands, so we could get this in place immediately and deploy it across the fleet via Ansible.</p><p></p><pre><code># Disable use of the EVAL and EVALSHA commands redis-cli ACL SETUSER {user} -EVAL -EVALSHA # Persist the change to config so it survives restarts redis-cli CONFIG REWRITE</code></pre><p></p><p>With that in place, we can take a little more time to investigate the issue and plan for the upgrade from Redis 8.2.1 to 8.2.2 to properly resolve the problem. </p><p></p><h4 id="upgrading-redis">Upgrading Redis</h4><p>As fate would have it, ~2 months ago I published this blog post: <a href="https://scotthelme.co.uk/were-going-high-availability-with-redis-sentinel/?ref=scotthelme.ghost.io" rel="noreferrer">We're going High Availability with Redis Sentinel!</a> In that blog post, I described our new High Availability setup using Redis Sentinel to give us failover capabilities between our new Primary and Replica caches. I also explained how one of the major benefits of the upgrade would be that updating our Redis server binary becomes a whole lot easier, and here we are!</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/10/image.png" class="kg-image" alt="CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded" loading="lazy" width="562" height="441"><figcaption><span style="white-space: pre-wrap;">Our Redis Sentinels fronting our Primary and Replica caches</span></figcaption></figure><p></p><p>With our new HA setup, it's simply a case of updating and upgrading the Replica cache, promoting it to Primary, and then updating what previously was the Primary cache but is now a Replica. Done!</p><pre><code>redis-cli info # Server redis_version:8.2.2 redis_git_sha1:00000000 redis_git_dirty:1 redis_build_id:8ade872ac64c6931 redis_mode:standalone ...</code></pre><p></p><h4 id="existing-protections-and-investigations">Existing protections and investigations</h4><p>Our Redis caches reside on a private network and are not at any time accessible from the public Internet. Further to that, we have firewall rules in place to only allow known IP addresses on our private network to have access to the Redis caches. Just to go the extra distance, we then only allow our application servers with the appropriate roles to have access to the appropriate Redis caches that they require to fulfil that role! Our network access is already locked down as tight as we can get it and there is no anomalous network activity in our logs.</p><p>Looking at the Redis caches themselves, we can use <code>commandstats</code> to get per-command counters on how many times each command has been called against the cache since the server started. To reset the counters it would require access to restart <code>redis-server</code> or call <code>CONFIG RESETSTAT</code>, and the command counters all look aligned with typical volumes before the update. Running the command, we can see no indication of the counts being reset, and there are no instances of <code>EVAL</code> or <code>EVALSHA</code> being called on the servers.</p><p></p><h4 id="further-hardening">Further Hardening</h4><p>With some quick research looking at other powerful commands, I could see that we currently don't require the use of the <code>SCRIPT</code> or <code>FUNCTION</code> commands, so they presented themselves as another good opportunity to further reduce the commands available. </p><p></p><pre><code># Disable use of the SCRIPT and FUNCTION commands redis-cli ACL SETUSER {user} -SCRIPT -FUNCTION # Persist the change to config so it survives restarts redis-cli CONFIG REWRITE</code></pre><p></p><p>There are some other commands that I want to restrict, but they require further investigation to ensure there are no adverse effects. To wrap things up and make sure that all of these restrictions are working as intended, we can try to run some of the commands that should now be blocked.</p><p></p><pre><code>redis-cli EVAL "return 1" 0 (error) NOPERM User default has no permissions to run the 'eval' command redis-cli ACL LOG 1 1) 1) "count" 2) (integer) 1 3) "reason" 4) "command" 5) "context" 6) "toplevel" 7) "object" 8) "eval" 9) "username" 10) "default" 11) "age-seconds" 12) "54.578" 13) "client-info" 14) "id=49 addr=*snip*:59092 laddr=*snip*:6379 fd=32 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=35 qbuf-free=20439 argv-mem=13 multi-mem=0 rbs=16384 rbp=16384 obl=0 oll=0 omem=0 tot-mem=37797 events=r cmd=eval user=default redir=-1 resp=2 lib-name= lib-ver= io-thread=0 tot-net-in=35 tot-net-out=0 tot-cmds=0" 15) "entry-id" 16) (integer) 0 17) "timestamp-created" 18) (integer) 1760018943783 19) "timestamp-last-updated" 20) (integer) 1760018943783</code></pre><p></p><p>The <code>EVAL</code> command was blocked as expected and the <code>ACL LOG</code> now has an entry to show that the block happened, which makes it easier to track the attempted usage of blocked commands in the future too.</p><p>With the fleet now updated to Redis 8.2.2, the additional protections in place, and no indication that we (or anyone else) were affected, I'm happy to consider this resolved!</p><p></p><h4 id="thats-a-lot-of-data">That's a lot of data!</h4><p>If you're curious just how much data we process, you can see my recent blog post <a href="https://scotthelme.co.uk/trillion-with-a-t-surpassing-2-trillion-events-processed/?ref=scotthelme.ghost.io" rel="noreferrer">Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀</a> which covers the details. That blog post also announced the launch of our public dashboard that provides a live insight into our telemetry volumes, along with heaps of other interesting data, so check that out here too: <a href="https://dash.report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Public Dashboard</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Capture JavaScript Integrity Metadata using CSP!]]></title><description><![CDATA[<p>Today we're announcing the open beta of a brand new and incredibly powerful feature on the Report URI platform, CSP Integrity! Having the ability to collect integrity metadata for scripts running on your site opens up a whole new realm of possibilities, and it couldn't be</p>]]></description><link>https://scotthelme.ghost.io/capture-javascript-integrity-metadata-using-csp/</link><guid isPermaLink="false">68bfe86ad26c1a00012b4a80</guid><category><![CDATA[Report URI]]></category><category><![CDATA[CSP]]></category><category><![CDATA[integrity]]></category><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 29 Sep 2025 10:51:09 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp" alt="Capture JavaScript Integrity Metadata using CSP!"><p>Today we're announcing the open beta of a brand new and incredibly powerful feature on the Report URI platform, CSP Integrity! Having the ability to collect integrity metadata for scripts running on your site opens up a whole new realm of possibilities, and it couldn't be simpler to get started. If you have a few seconds spare, you have enough time to get started now!</p><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/09/report-uri.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/09/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/09/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="a-revolution-for-content-security-policy">A revolution for Content Security Policy</h4><p>Content Security Policy (CSP) is an incredibly powerful mechanism, allowing you to leverage control over a wide range of resources on your site. You can control where resources are loaded from or where data is sent to, and you can be alerted when things don't go according to plan. But regular readers will already know all of the existing benefits of CSP, so today, let's focus on the latest addition to the CSP arsenal; the collection of integrity metadata.</p><p>It's so easy to get started that we can boil it down to two simple steps. </p><ol><li>Ask the browser to send integrity metadata for scripts.</li><li>Tell it where to send the integrity metadata for scripts.</li></ol><p></p><p>That's really all there is to it, so let's break it down. If you already have a CSP in place, you will need to add the <code>'report-sha256'</code> keyword to your <code>script-src</code> directive, and if you don't have a CSP in place, you can add one with just this directive. This is what your CSP <code>script-src</code> might look like after you add the new keyword, which I've highlighted in bold:</p> <!--kg-card-begin: html--> <pre>script-src 'self' some-cdn.com <b>'report-sha256'</b>;</pre> <!--kg-card-end: html--> <p></p><p>Now that you've added the new keyword to your policy, the browser will send integrity metadata for scripts that load on your site, but you will need to tell the browser where to send it, and for that, we will use the <a href="https://scotthelme.co.uk/introducing-the-reporting-api-nel-other-major-changes-to-report-uri/?ref=scotthelme.ghost.io" rel="noreferrer">Reporting API</a>. All you have to do to enable this is add a new response header:</p><pre><code>Report-To: {"group":"default","max_age":31536000,"endpoints":[{"url":"https://scotthelme.report-uri.com/a/d/g"}],"include_subdomains":true}</code></pre><p></p><p>This defines a new group called <code>default</code> and sets the destination for telemetry data to <code>https://scotthelme.report-uri.com/a/d/g</code> . Please note that you should copy this value from the Setup page in <em>your</em> Report URI account as it will be unique to you and the one shown here is my unique URL for demonstration purposes. </p><p>With that, we can now link the two together by adding the <code>report-to</code> directive to your CSP:</p> <!--kg-card-begin: html--> <pre>script-src 'self' some-cdn.com 'report-sha256'; <b>report-to default;</b></pre> <!--kg-card-end: html--> <p></p><p>All you have to do now is sit back and wait for the telemetry to start rolling in!</p><p></p><h4 id="surfacing-this-in-the-ui">Surfacing this in the UI</h4><p>Processed in the same way that any telemetry sent to us would be, this data will only take a few minutes to start showing up in your dashboard. I've used our 'aggregate' search filter here to grab a list of unique dependencies that have been loaded on our site, and we can start to see the value we can extract from this data.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="1531" height="870" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/09/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/09/image.png 1000w, https://scotthelme.ghost.io/content/images/2025/09/image.png 1531w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If we focus on just one of these entries, we can see that a Bootstrap JS file has been loaded.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image-1.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="525" height="97"></figure><p></p><p>The browser has reported that the hash of the asset is <code>sha256-wMCQIK229gKxbUg3QWa544ypI4OoFlC2qQl8Q8xD8x8=</code>, which is helpful in various different ways. If we were loading this file from an external location for example, we could now track the hash over time to see if the file we are being served has changed. We can also use the hash to try and identify if the file we were served is the file we were expecting.</p><p></p><h4 id="identifying-verified-javascript-files">Identifying verified JavaScript files</h4><p>As you can see from the screenshot above, we have identified that the particular file loaded was a verified file from the 'bootstrap' package, hence the green marker with the library name. Using the hash as a fingerprint to uniquely identify a file is a common approach, but you need to have a reliable and <em>verified</em> library of fingerprints to reference against in order to lookup the fingerprint and know for sure. This is something that we've been working hard to build and so far we have verified fingerprints for <strong><em>4,268,847</em></strong> unique JS files from common packages, libraries and dependencies across the Web! As we have the fingerprints for each of those files stored in their sha256, sha 384 and sha512 variants, it means we have a running total of <strong><em>12,806,541</em></strong> fingerprints in our database so far! This is a huge amount of valuable information to draw from, and it's something that's available right there in the dashboard.</p><p>That's not all, though, there's something even better to add!</p><p></p><h4 id="identifying-javascript-vulnerabilities">Identifying JavaScript vulnerabilities</h4><p>You may notice that the marker for that file is green, and that means that there are no known issues reported with that particular version of that file/library. If you keep scrolling through your data, you might, if you're unlucky, come across a file that has a red marker instead...</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image-2.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="525" height="95"></figure><p></p><p>This particular file is from a version of the Bootstrap library that has a known security vulnerability, and we can now surface that information directly to you in our UI. If you click the warning, it will open a small dialog box to give you some quick information on what the minimum version of the library you need to upgrade to is, and it will even link out to the disclosure notice for the vulnerability.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/09/image-3.png" class="kg-image" alt="Capture JavaScript Integrity Metadata using CSP!" loading="lazy" width="527" height="223"></figure><p></p><p>This will be an awesome feature to have, allowing you to keep a close eye on exactly how problematic your JS dependencies are, and potentially avoiding costly issues from using dependencies with known vulnerabilities. </p><p></p><h4 id="looking-at-the-new-telemetry-payload">Looking at the new telemetry payload</h4><p>Defined in the specification as a <code>csp-hash</code> report, it has a very familiar JSON payload, similar to your typical CSP violation report. Here's a sample of one of our payloads:</p><p></p><pre><code>{ "csp-hash": { "destination": "script", "documentURL": "https://report-uri.com/login/", "hash": "sha256-wMCQIK229gKxbUg3QWa544ypI4OoFlC2qQl8Q8xD8x8=", "subresourceURL": "https://cdn.report-uri.com/libs/refresh/bootstrap/bootstrap.bundle.min.js", "type": "subresource" } }</code></pre><p></p><p>Breaking the fields inside this payload down, it's fairly self-explanatory:</p><p><code>destination</code> - Only <code>script</code> is supported for now, with new destinations being planned for future updates.</p><p><code>documentURL</code> - This is the URL the browser was visiting when the reported resource was observed.</p><p><code>hash</code> - This is the hash of the resource, which in this case is the sha256 hash as that is what we requested.</p><p><code>subresourceURL</code> - This is the URL of the script that was requested and the hash is of the content of this file.</p><p><code>type</code> - Only <code>subresource</code> is supported for now, with the ability to expand to new types in the future.</p><p></p><p>Now, with the <code>'report-sha256'</code> keyword in place, one of these payloads will be sent for each script that loads on your page!</p><p></p><h4 id="quick-tips">Quick Tips</h4><p>I'm sure many people will have some questions that spring to mind after reading this, so let me address some of the most common ones that I encountered during our closed beta testing. </p><p></p><h6 id="do-i-need-to-build-a-full-csp">Do I need to build a full CSP?</h6><p>No! You don't need any CSP at all, you can start monitoring scripts and collecting integrity metadata right away:</p><pre><code>Content-Security-Policy-Report-Only: script-src 'report-sha256'; report-to default</code></pre><p></p><h6 id="is-integrity-metadata-sent-for-scripts-that-load-or-scripts-that-are-blocked">Is integrity metadata sent for scripts that load, or scripts that are blocked?</h6><p>Integrity metadata is only sent for scripts that load on your page. If a script is blocked, it is not requested, so there is no file to provide integrity metadata for.</p><p></p><h6 id="will-this-use-a-lot-of-my-event-quota">Will this use a lot of my event quota?</h6><p>No, we're going to downsample CSP Integrity reports for all customers at a rate of 1/10, so whilst it will of course use some event quota, it should not use excessive amounts.</p><p></p><h6 id="what-happens-after-the-open-beta">What happens after the open beta?</h6><p>CSP Integrity will become an add-on feature for customers on our Ultimate plan and customers on an Enterprise plan in 2026.</p><p></p><h4 id="get-started-now">Get started now</h4><p>I will be announcing a webinar to go through the new CSP Integrity feature so keep an eye out for that, but everything you need to get started is right here. It really is as simple as adding those two new values to your CSP and just waiting for the valuable data to start rolling in!</p><p></p>]]></content:encoded></item><item><title><![CDATA[We're going High Availability with Redis Sentinel!]]></title><description><![CDATA[<p>We've just deployed some mega updates to our infrastructure at Report URI that will give us much more resilience in the future, allow us to apply updates to our servers even faster, and will probably go totally unnoticed from the outside!</p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/08/report-uri.png" class="kg-image" alt loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/08/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="our-previous-redis-setup">Our previous Redis setup</h4><p>I've</p>]]></description><link>https://scotthelme.ghost.io/were-going-high-availability-with-redis-sentinel/</link><guid isPermaLink="false">68976e9629f3b600011277d4</guid><category><![CDATA[Report URI]]></category><category><![CDATA[Redis]]></category><category><![CDATA[Sentinel]]></category><category><![CDATA[High Availability]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 18 Aug 2025 12:46:41 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp" alt="We're going High Availability with Redis Sentinel!"><p>We've just deployed some mega updates to our infrastructure at Report URI that will give us much more resilience in the future, allow us to apply updates to our servers even faster, and will probably go totally unnoticed from the outside!</p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/08/report-uri.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="946" height="525" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/08/report-uri.png 946w" sizes="(min-width: 720px) 720px"></a></figure><h4 id="our-previous-redis-setup">Our previous Redis setup</h4><p>I've talked about our infrastructure openly before, but I will give a brief overview of what we were working with and why I wanted to upgrade. We were using Redis in two places, one instance for our application session store, which is fairly self explanatory, and one instance for our inbound telemetry cache. Both of these isolated instances of Redis have been upgraded in the same way, but I will focus on the telemetry cache as this is the one that faces a significant amount of load! Here's how it works.</p><p>Visitors head to the websites of our customers, and they may or may not send some telemetry to us depending on whether there are any security, performance or other concerns to report to us about the website they were visiting. These telemetry events are received and are placed directly in to Redis because there are simply too many for us to process them in real-time. Our servers, that we refer to as 'consumers', will then feed telemetry from this Redis cache and process it, applying normalisation, filtering, checking it against our Threat Intelligence, and doing a whole heap of other work, before placing it in persistent storage, at which point it becomes available to customers in their dashboards. This 'ingestion pipeline' can take anywhere from 20-30 seconds during quiet periods, and spike up to 6-7 minutes during our busiest periods, but it is reliable and consistent. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-10.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="883" height="187" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-10.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-10.png 883w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If you'd like to see some real data about the volumes of telemetry we process, what our peaks and troughs look like, and a whole bunch of other interesting metrics, you should absolutely check out our <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">Public Dashboard</a>. That has everything from telemetry volumes, client types, regional traffic patterns, a global heat map and even a Pew Pew Map!!</p><p></p><h4 id="a-single-point-of-failure">A single point of failure</h4><p>Both of our caches, the session cache and the telemetry cache, were single points of failure, and it's never a good idea to have a single point of failure. We've never had an issue with either of them, but it's been something I've wanted to address for a while now. Not only that, but it makes updating and upgrading them much harder work too. The typical process for our upgrades has been to bring up a new server, fully patch and update it and then deploy Redis along with our configuration, with the final step being a data migration consideration. Using Ansible to do the heavy lifting makes this process easier, but not easy. The session cache of course needs a full data migration and then the application servers can flip from the old one to the new one, which isn't so bad because the data is relatively small. The telemetry cache is a little trickier because it involves pointing the inbound telemetry to the new cache, while allowing the consumers to drain down the old telemetry cache before moving them over too. As I say, it's not impossible, but it could be a lot better, and we still have that concern of them both being a single point of failure. </p><p></p><h4 id="redis-sentinel-to-the-rescue">Redis Sentinel to the rescue!</h4><p>You can read the official docs on <a href="https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/?ref=scotthelme.ghost.io" rel="noreferrer">Redis Sentinel</a> but I'm going to cover everything you need to know here if you'd like to keep reading. Redis Sentinel provides a few fundamental features that we're now leveraging to provide a much more resilient service. </p><p><strong>Monitoring:</strong> Redis Sentinels can monitor a pool of Redis caches to determine if they are healthy and available. </p><p><strong>Failover:</strong> Redis Sentinels will promote and demote Redis caches between the roles of Primary and Replica based on their health. </p><p><strong>Configuration:</strong> Clients connect to a Redis Sentinel to get the ip/port for the current Primary cache.</p><p></p><p>Leveraging these features, we've now created a much more robust solution that looks like this:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-8.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="1317" height="1034" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-8.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-8.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-8.png 1317w" sizes="(min-width: 720px) 720px"></figure><p></p><p>To give an overview of what's happening here, I will break it down in to the following:</p><p><strong>Redis Caches:</strong> These have an assigned role of either Primary or Replica, and for the purposes of our diagram, the red one will be the Primary and the yellow one will be the Replica. If you have more than two caches in your pool, there would be more Replicas. The role of the Replica is to keep itself fully aligned with the Primary so it can become the Primary at any point if needed. The role of the Primary is to serve the clients as their Redis cache and is where our inbound telemetry is being sent.</p><p><strong>Redis Sentinels:</strong> These severs do a few things, so let's break it down.</p><ol><li>They're providing ip/port information to clients about which cache is the current Primary. Clients don't know about the caches or how many there are, nor do they know which one is the Primary, so they first ask a Sentinel which cache is the current Primary and then connect to that one. Note that no cache activity passes through the Sentinels, all they do is provide the information to the client on where to connect, and the client will then connect directly to the cache.</li><li>They're monitoring the health of the Primary and Replica/s to detect any issues. If a problem is detected with the Primary, it is put to a vote to see if a Replica should be promoted to Primary. Our configuration requires a quorum of 3/4 votes to successfully promote a Replica to Primary, and demote the Primary to a Replica when it returns. Once that happens, the Sentinels will reconfigure all caches to follow the new Primary, and start providing the new Primary ip/port information to clients.</li><li>They're always monitoring for the addition of new caches or Sentinels. There is no configuration for how many Sentinels or caches there are, they are discovered by talking to the current Primary and getting information on how many Replicas and Sentinels are connected to that Primary.</li></ol><p></p><h4 id="failing-over">Failing over</h4><p>Of course, all of this work is designed to give us a much more resilient solution, and it will also make our work for installing updates and upgrades much easier too. There are two scenarios when the Sentinels can failover from a Primary to a Replica, and they are when the Sentinels detect an issue with the Primary and vote to demote it, or when we manually trigger a failover.</p><p>When we're getting ready to do some upgrades, the first step will be to take the Replica down so the Sentinels notice and remove it as an option for a current failover. We can then update/upgrade that server and bring it back online as a viable Replica. Now, when we want to upgrade the cache that is the current Primary, we trigger the failover and the Sentinels will take care of it, meaning our infrastructure now looks like this with the new roles.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-9.png" class="kg-image" alt="We're going High Availability with Redis Sentinel!" loading="lazy" width="1308" height="1032" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-9.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-9.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-9.png 1308w" sizes="(min-width: 720px) 720px"></figure><p></p><p>If there is a problem with the update/upgrade, we can immediately fail back, giving us a nice recovery option, and if everything goes well, we can now follow the same process of taking the Replica down for updates/upgrades. Once that's done, both servers are fully updated with considerably less work than before!</p><p></p><h4 id="other-considerations">Other considerations</h4><p>Whilst working through this project there were a few little details that I picked up along the way that might be useful to share, and also some that are probably a bit more specific to our deployment. </p><ul><li>Our Redis Sentinels are running on dedicated servers, but I saw many recommendations to co-locate a Redis Sentinel service alongside the Redis Server service on the cache servers themselves. I decided against this for a couple of reasons. First, I didn't want to lose a Sentinel when we took a cache down for updates/upgrades because this will impact the total number of Sentinels available to clients, and it has implications for voting too. Second, I wanted to take that load away from the caches so they could focus on being caches, meaning each of our servers is focusing on doing one thing well.</li><li>Your quorum for voting must be >50% of the number of Sentinels you have, it must be a majority vote to promote a new Primary. With our four Sentinels, if we had a quorum of two, we have less protection against something called a Split-Brain happening. As an example, let's say we have two caches, Redis Cache 1 and Redis Cache 2. Two of our four Sentinels have a transient network issue and see the Primary (Redis Cache 1) as being down, so they vote between themselves to promote a Replica (Redis Cache 2), and then begin that process. Because they can't communicate with the previous Primary (Redis Cache 1), they can't demote it to a Replica. Meanwhile, the other two Sentinels see no issue and continue as normal with their existing Primary (Redis Cache 1). You now have Sentinels providing ip/port information for two different Primaries that are both accepting writes from clients and their data is now diverging, a Split-Brain! Having a quorum require a majority of your Sentinels to vote doesn't fully solve this issue, but it does give you a lot more protection against it.</li><li>If you have a very read-heavy workload, your Replicas can share the load as they do support read-only connections. In our two scenarios we can't really leverage this benefit, which is a real shame, but being able to distribute reads across your replicas may be a huge benefit to you.</li></ul><p></p><h4 id="nobody-would-know">Nobody would know!</h4><p>Whilst this was quite a huge change on our side, from the outside, nobody would know anything about this if I hadn't published this blog post! Going forwards, this will allow me to sleep a lot better knowing we have a more resilient service, and that our future updates and upgrades will now take much less time and effort than before. If you have any feedback or suggestions on our deployment, feel free to drop by the comments below. </p><p></p>]]></content:encoded></item><item><title><![CDATA[Automation improvements after a Tesla Powerwall outage!]]></title><description><![CDATA[<p>So, a weird thing happened over the last couple of days, and my Tesla Powerwalls weren't working properly, or, at all, actually... What's even more strange is that Tesla has been completely silent about this and hasn't made a single announcement about the issue</p>]]></description><link>https://scotthelme.ghost.io/automation-improvements-after-a-tesla-powerwall-outage/</link><guid isPermaLink="false">68974a0c29f3b6000112773f</guid><category><![CDATA[Tesla Powerwall]]></category><category><![CDATA[Home Assistant]]></category><category><![CDATA[Teslemetry]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 11 Aug 2025 09:49:48 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp" alt="Automation improvements after a Tesla Powerwall outage!"><p>So, a weird thing happened over the last couple of days, and my Tesla Powerwalls weren't working properly, or, at all, actually... What's even more strange is that Tesla has been completely silent about this and hasn't made a single announcement about the issue that I can find, and I haven't been notified.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-1.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="600" height="211" srcset="https://scotthelme.ghost.io/content/images/2025/08/image-1.png 600w"></figure><p></p><h4 id="my-home-setup">My Home Setup</h4><p>You can read various posts on my blog about how I automate almost all of my home with Home Assistant, and more recently there were two Tesla Powerwall posts [<a href="https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">1</a>][<a href="https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">2</a>] about bringing the final pieces of the puzzle together. I have everything working almost exactly how I want it to, with the one outstanding question on how I can control the charge rate of the Powerwalls, which I will be answering later!</p><p>First, though, this recent Tesla outage that I've seen absolutely nothing from Tesla about... Regular readers will know I automate my Powerwalls via <a href="https://www.home-assistant.io/?ref=scotthelme.ghost.io" rel="noreferrer">Home Assistant</a> using the <a href="https://teslemetry.com/?ref=scotthelme.ghost.io" rel="noreferrer">Teslemetry</a> service. </p><p></p><figure class="kg-card kg-image-card"><a href="https://teslemetry.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-2.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="731" height="76" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-2.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-2.png 731w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>I can control when the Powerwalls are charging or not based on the current cost for electricity import, and I can also control when they're exporting to the grid if I have excess capacity for the day. Things were going really quite well, until I woke up one morning and the batteries were basically empty. I woke up on the morning of the 7th August and we hadn't charged the batteries in the cheap off-peak tariff that we get overnight, which is really the main point of the batteries. Fill up on the cheap stuff overnight, avoid the expensive stuff during the day.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-3.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="485" height="345"></figure><p></p><p>I tried to enable grid charging to see if I could just get some power back in to the batteries so they weren't sat on such a low SoC, but it wouldn't enable. I wondered if Teslemetry were having an issue, but nothing was reported on their side. I disabled the integration and went directly to the official Tesla app and tried to enable grid charging there, but again, it wouldn't enable. After some searching, it seems it wasn't just me having the issue and anyone with a Powerwall setup seemed to be having the exact same problem. Shortly after, an incident went up on the Teslemetry <a href="https://status.teslemetry.com/incident/701944?ref=scotthelme.ghost.io" rel="noreferrer">status page</a> saying that the issue was with the Tesla API.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-4.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="834" height="517" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-4.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-4.png 834w" sizes="(min-width: 720px) 720px"></figure><p></p><p>It wasn't just Teslemetry, either. Other services that also allow for control of Tesla Powerwalls were having issues and reporting similar problems on their status pages too, like <a href="https://docs.netzero.energy/docs/tesla/2025-08/GridChargingIssue.html?ref=scotthelme.ghost.io" rel="noreferrer">NetZero</a>.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/08/image-5.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="1003" height="398" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-5.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-5.png 1003w" sizes="(min-width: 720px) 720px"></figure><p></p><p>As you can see from the graph above, I did manage to capture some excess solar production during the day and charge the batteries a little, but it was almost 24 hours later when the issue finally resolved and the button to enable grid charging started magically working again! I enabled the Teslemetry integration and we were back in business. </p><p></p><h4 id="so-whats-up-with-that">So what's up with that?..</h4><p>I genuinely do find it staggering that a trillion dollar company can't notify customers who are actively using their product of an issue such as this! It's pretty crazy when you think about it that there was an issue that effectively rendered Powerwalls useless, and there have been no notifications, updates, or a post-mortem, nothing... This is really poor from Tesla and I think this issue deserves more attention.</p><p>Part of me is also wondering if there might be a problem with enabling a huge amount of Powerwalls to be able to grid charge again all at once. Could it have an impact on the grid? Was that part of the problem and they staggered the rollout of the fix? What was the problem? I just want to know! </p><p><strong><em>Update 11th Aug 15:17 BST:</em></strong> Tesla responded to my support ticket with the following:</p><blockquote>We are reaching out regarding your inquiry that grid charging turns it self off automatically. Our App Team is aware of this issue and is currently working on resolving it.<br>Please rest assured, we will resolve it within the next Tesla App updates.</blockquote><p></p><p></p><h4 id="controlling-how-fast-your-powerwalls-charge">Controlling how fast your Powerwalls charge</h4><p>One of the last things I had to address in my previous blog post was controlling how fast my Powerwalls were charging. Once you set them to charge, they charge up at full power, which isn't really necessary. Charging them at such a high rate isn't necessary as I have a long time to charge them, and charging at a higher rate is worse for the battery, and it leaves it sat at 100% for a longer period overnight, which is also worse for the battery! Overall that's a bad pair of options, but what if you could change the rate at which the batteries draw power when charging? Well, you can!</p><p>This is super easy to do thanks to my use of Home Assistant and Teslemetry, and it's worth pointing out that one of the options below won't be available to you if you're not using a service like Teslemetry as it's not available in the Tesla app! It turns out that when the batteries are in a different mode, they charge at a different rate. Here is the operation mode you need to set in Teslemetry, and in brackets is the name of that mode in the Tesla app.</p><p></p><table> <thead> <tr> <th>Operation Mode</th> <th>Total Draw</th> <th>Per Battery</th> </tr> </thead> <tbody> <tr> <td>Autonomous (Time-Based Control)</td> <td>15kW</td> <td>5kW</td> </tr> <tr> <td>Self-consumption (Self-Powered)</td> <td>5.7kW</td> <td>1.9kW</td> </tr> <tr> <td>Backup (not available)</td> <td>11.1kW</td> <td>3.7kW</td> </tr> </tbody> </table> <p></p><p>I have 3 x Powerwalls so the above numbers show my total grid power draw when charging, and what the breakdown is per Powerwall. I've always been using "Autonomous" mode, which is known as "Time-Based Control" in the Tesla app, and that results in the batteries charging at their maximum rate of 5kW each!</p><p>It turns out that if you put the Powerwalls in "Self-consumption" mode, which is known as "Self-Powered" in the Tesla app, they only charge at 1.9kW each. That might be helpful to you, but for me, it's not enough. The Powerwalls have an official usable capacity of 13.5kWh, and mine are reporting a usable capacity of either 13.7kWh or 13.8kWh via the API. My cheap rate tariff applies from 23:30 to 05:30 giving me 6 hours and unfortunately, 6 hours x 1.9kW = 11.4kWh, which is not enough to fully recharge them.</p><p>The good news is that because I'm using the Teslemetry integration, it gives me access to the "Backup" mode, which isn't available in the Tesla app, and it sees the batteries charge at ~3.7kW, which works out quite well because 13.8kWh / 3.7kW = 3.7 hours (3hrs 45min). That means the batteries can charge at a lower rate and take a longer time to hit 100% SoC, but still be comfortably ready by 05:30 even if I need to go from 0% to 100%. Another bonus is that Backup mode will automatically set the Backup Reserve to 100% too! For comparison, here is the charging curve before and after the change, showing that slightly gentler curve now I'm using Backup mode.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/08/Screenshot-2025-08-09-152600.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="477" height="343"><figcaption><span style="white-space: pre-wrap;">Batteries charging at 15kW</span></figcaption></figure><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/08/Screenshot-2025-08-09-152614.png" class="kg-image" alt="Automation improvements after a Tesla Powerwall outage!" loading="lazy" width="475" height="342"><figcaption><span style="white-space: pre-wrap;">Batteries charging at 11.1kW</span></figcaption></figure><p></p><h4 id="my-current-automations">My current automations</h4><p>Given the above issue with the Tesla API, I wanted to avoid turning Grid Charging on and off because I might find myself in a position where I can't turn it back on again at some point in the future... Going forwards, I'm going to control charging by toggling the operation mode between Autonomous and Backup, whilst setting the Backup Reserve to the appropriate level too. I will also provide a copy of my export automation for completeness too. Here is the battery charging automation:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Dynamic Battery Charging Control description: | Sets Shireburn operation mode and backup reserve based on price and SoC. - Cheap & below target SoC => backup @ 100% - Expensive or above target SoC => autonomous @ target SoC triggers: - trigger: time_pattern minutes: /5 conditions: [] actions: - choose: - conditions: - condition: numeric_state entity_id: >- sensor.octopus_energy_electricity_snip_current_rate below: input_number.electricity_cost_threshold - condition: numeric_state entity_id: sensor.shireburn_charge below: input_number.powerwall_soc_threshold sequence: - condition: or conditions: - condition: template value_template: "{{ states('select.shireburn_operation_mode') != 'backup' }}" - condition: template value_template: "{{ states('number.shireburn_backup_reserve') | int != 100 }}" - action: select.select_option target: entity_id: select.shireburn_operation_mode data: option: backup - action: number.set_value target: entity_id: number.shireburn_backup_reserve data: value: 100 - conditions: - condition: numeric_state entity_id: >- sensor.octopus_energy_electricity_snip_current_rate above: input_number.electricity_cost_threshold sequence: - condition: or conditions: - condition: template value_template: >- {{ states('select.shireburn_operation_mode') != 'autonomous' }} - condition: template value_template: "{{ states('number.shireburn_backup_reserve') | int != 0 }}" - action: select.select_option target: entity_id: select.shireburn_operation_mode data: option: autonomous - action: number.set_value target: entity_id: number.shireburn_backup_reserve data: value: 0 mode: single </code></pre><p></p><p>Here is the export automation:</p><p></p><pre><code class="language-yaml">alias: Dynamic Battery Export Control description: >- Adjust export setting and export mode display based on battery SoC and time-of-day triggers: - minutes: /5 trigger: time_pattern actions: - variables: now_ts: "{{ now().timestamp() }}" today: "{{ now().date().isoformat() }}" start_ts: "{{ (today ~ 'T05:30:00') | as_datetime | as_timestamp }}" end_ts: "{{ (today ~ 'T23:30:00') | as_datetime | as_timestamp }}" end_for_target_ts: "{{ (today ~ 'T23:15:00') | as_datetime | as_timestamp }}" current_soc: "{{ states('sensor.shireburn_charge') | float(0) }}" - choose: - conditions: - condition: template value_template: "{{ now_ts < start_ts or now_ts > end_ts }}" sequence: - target: entity_id: input_number.target_soc data: value: 100 action: input_number.set_value - target: entity_id: input_text.export_mode_state data: value: No Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'never' }}" - target: entity_id: select.shireburn_allow_export data: option: never action: select.select_option default: - variables: total_secs: "{{ end_for_target_ts - start_ts }}" elapsed_secs: "{{ now_ts - start_ts }}" progress: >- {{ (elapsed_secs / total_secs) if elapsed_secs < total_secs else 1.0 }} target_soc: "{{ 100 - (95 * (progress ** 1.5)) }}" - target: entity_id: input_number.target_soc data: value: "{{ target_soc | round(0) }}" action: input_number.set_value - choose: - conditions: - condition: template value_template: | {% set hour = now().hour %} {% if hour < 23 %} {{ current_soc > (target_soc + 5) }} {% else %} {{ current_soc > target_soc }} {% endif %} sequence: - target: entity_id: input_text.export_mode_state data: value: Battery + Solar Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'battery_ok' }}" - target: entity_id: select.shireburn_allow_export data: option: battery_ok action: select.select_option - conditions: - condition: template value_template: "{{ current_soc < (target_soc - 5) }}" sequence: - target: entity_id: input_text.export_mode_state data: value: No Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'never' }}" - target: entity_id: select.shireburn_allow_export data: option: never action: select.select_option default: - target: entity_id: input_text.export_mode_state data: value: Solar Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'pv_only' }}" - target: entity_id: select.shireburn_allow_export data: option: pv_only action: select.select_option mode: single </code></pre><p></p><p>Hopefully they will be useful and perhaps we might know more about the Tesla API outage soon!..</p><p></p><p></p> <!--kg-card-begin: html--> <style> pre[class*="language-"], code[class*="language-"] { font-size: 0.85em !important; } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer"> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js" integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js" integrity="sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <!--kg-card-end: html--> ]]></content:encoded></item><item><title><![CDATA[OWASP ASVS 5.0.0 is here!]]></title><description><![CDATA[<p>I've been a huge fan of OWASP for a very long time, having spoken at their conferences, contributed to their projects, consumed many of their resources and met some really awesome people along the way! Just recently, one of the very popular OWASP projects, the <a href="https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io" rel="noreferrer">Application Security Verification</a></p>]]></description><link>https://scotthelme.ghost.io/owasp-asvs-5-0-0-is-here/</link><guid isPermaLink="false">684ffc0ea1c29300018cdf43</guid><category><![CDATA[OWASP]]></category><category><![CDATA[ASVS]]></category><category><![CDATA[Report URI]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 04 Aug 2025 10:07:02 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg" alt="OWASP ASVS 5.0.0 is here!"><p>I've been a huge fan of OWASP for a very long time, having spoken at their conferences, contributed to their projects, consumed many of their resources and met some really awesome people along the way! Just recently, one of the very popular OWASP projects, the <a href="https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io" rel="noreferrer">Application Security Verification Standard</a> (ASVS), had a major update released and it brought with it some exciting changes. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/image-5.png" class="kg-image" alt="OWASP ASVS 5.0.0 is here!" loading="lazy" width="2000" height="500" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/image-5.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/06/image-5.png 1600w, https://scotthelme.ghost.io/content/images/size/w2400/2025/06/image-5.png 2400w" sizes="(min-width: 720px) 720px"></figure><p></p><h4 id="owasp-asvs">OWASP ASVS</h4><p>I'd bet that most people are familiar with the <a href="https://owasp.org/www-project-top-ten/?ref=scotthelme.ghost.io" rel="noreferrer">OWASP Top 10</a> project, one of the most notable flagship projects to come out of OWASP! </p><blockquote>The OWASP Top 10 is a standard awareness document for developers and web application security. It represents a broad consensus about the most critical security risks to web applications.</blockquote><p></p><p>For me, coming in at a close second place is the <a href="https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io" rel="noreferrer">OWASP ASVS</a> project, another project aimed at helping the community with a structured approach to taking care of application security.</p><blockquote>The OWASP Application Security Verification Standard (ASVS) Project provides a basis for testing web application technical security controls and also provides developers with a list of requirements for secure development.</blockquote><p></p><p>One of the things I love about the ASVS is the straightforward, "no nonsense" approach. They aren't requirements imagined in a vacuum, beyond the realistic hope or requirement of anyone trying to implement, instead they seek to be effective and balanced. This quote from the early paragraphs of the document in the 'Level evaluation' section, where you consider which level of compliance is appropriate for you, says a lot:</p><blockquote>Levels are defined by priority‑based evaluation of each requirement based on experience implement‑ ing and testing security requirements. The main focus is on comparing risk reduction with the effort to implement the requirement. Another key factor is to keep a low barrier to entry.</blockquote><p></p><p>I've provided a copy of the standard below and if you're not familiar with ASVS, I'd really recommend checking it out, even if only for you to use it as a reference rather than a hard requirement. </p><div class="kg-card kg-file-card"><a class="kg-file-card-container" href="https://scotthelme.ghost.io/content/files/2025/06/OWASP_Application_Security_Verification_Standard_5.0.0_en.pdf" title="Download" download><div class="kg-file-card-contents"><div class="kg-file-card-title">OWASP_Application_Security_Verification_Standard_5.0.0_en</div><div class="kg-file-card-caption"></div><div class="kg-file-card-metadata"><div class="kg-file-card-filename">OWASP_Application_Security_Verification_Standard_5.0.0_en.pdf</div><div class="kg-file-card-filesize">504 KB</div></div></div><div class="kg-file-card-icon"><svg viewbox="0 0 24 24"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class="a" points="8.25 14.25 12 18 15.75 14.25"/><line class="a" x1="12" y1="6.75" x2="12" y2="18"/><circle class="a" cx="12" cy="12" r="11.25"/></svg></div></a></div><p></p><figure class="kg-card kg-image-card"><a href="https://report-uri.com/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/06/Report-URI-Twitter-Card.png" class="kg-image" alt="OWASP ASVS 5.0.0 is here!" loading="lazy" width="800" height="400" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/Report-URI-Twitter-Card.png 600w, https://scotthelme.ghost.io/content/images/2025/06/Report-URI-Twitter-Card.png 800w" sizes="(min-width: 720px) 720px"></a></figure><p></p><h4 id="the-parts-where-we-can-help">The parts where we can help</h4><p>As a platform focused on providing organisations with the tools they need to be secure, there is of course a huge overlap between the requirements in the OWASP ASVS 5.0.0 standard, and <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>'s wide range of products and features. Some of these requirements are more generalised and are something we can help with on a more holistic approach, and some of them are slam-dunk cases where Report URI can 100% help you meet that requirement. We'll go through these requirements at a high level and look at how Report URI can help, and we'll also provide additional guidance and context to help you understand the requirements better. Let's start with the big hitters first, and go from there, and also remember to check the compliance level which we will provide for each requirement to see if it's something you will need to consider. Here's a quick summary of the compliance levels if you aren't familiar with them.</p><p>✅ <strong>Level 1 — Informational / Low Assurance</strong><br>Low-risk apps (e.g. simple brochure sites or MVPs).</p><p><strong>✅✅ Level 2 — Standard Assurance (Baseline Compliance)</strong><br>Apps handling personal data, business logic, or moderate sensitivity.</p><p><strong>✅✅✅ Level 3 — High Assurance</strong><br>Financial systems, healthcare apps, and other high-value or high-impact systems.</p><p></p><p>You can assess which level of the requirements is suitable for your application, and then you can use that to guide you through the following sections to determine which apply to you and your application.</p><p></p><h4 id="v31-web-frontend-security-documentation">V3.1 Web Frontend Security Documentation</h4><p>This is a pretty sensible requirement to protect against the impact of malicious content find its way in to your site, and as a Level 1 requirement, is applicable to all sites.</p><p></p><p><strong>3.2.1 (Level 1)</strong></p><blockquote>Verify that security controls are in place to prevent browsers from rendering content or functionality in HTTP responses in an incorrect context (e.g., when an API, a user‑uploaded file or other resource is requested directly). Possible controls could include: not serving the content unless HTTP request header fields (such as Sec‑Fetch‑*) indicate it is the correct context, using the sandbox directive of the Content‑Security‑Policy header field or using the attachment disposition type in the Content‑Disposition header field. </blockquote><p></p><p>Whenever you're using Content Security Policy, it's <em>always </em>advisable to monitor that policy and ensure there isn't anything happening that you weren't expecting. The sandbox directive is particularly powerful in the suggested context and allows you to isolate the downloaded content.</p><p></p><h4 id="v34-browser-security-mechanism-headers">V3.4 Browser Security Mechanism Headers </h4><p>Honestly at this point in the realm of security online, who doesn't love Security Headers?! There is so much powerful protection you can leverage by configuring these relatively simple mechanisms, it's no wonder that they got an entire section in the standard. You should read this entire section, absolutely, but here are the parts we can help with specifically.</p><p></p><p><strong>3.4.3 (Level 2 and Level 3)</strong></p><blockquote>Verify that HTTP responses include a Content‑Security‑Policy response header field which defines directives to ensure the browser only loads and executes trusted content or resources, in order to limit execution of malicious JavaScript. As a minimum, a global policy must be used which includes the directives object‑src ‘none’ and base‑uri ‘none’ and defines either an allowlist or uses nonces or hashes. For an L3 application, a per‑response policy with nonces or hashes must be defined.</blockquote><p></p><p><strong>3.4.6 (Level 2)</strong></p><blockquote>Verify that the web application uses the frame‑ancestors directive of the Content‑Security‑Policy header field for every HTTP response to ensure that it cannot be embedded by default and that embedding of specific resources is allowed only when necessary. Note that the X‑Frame‑Options header field, although supported by browsers, is obsolete and may not be relied upon.</blockquote><p></p><p><strong>3.4.7 (Level 3)</strong></p><blockquote>Verify that the Content‑Security‑Policy header field specifies a location to report violations.</blockquote><p></p><p>That last one is particularly noteworthy, and I'd say that any application with a CSP in place should be monitoring for those violations because you want to know if things have been configured incorrectly, or worse, an attack is currently ongoing! All of that guidance is sensible though and these are all standard things that we'd be recommending to our customers anyway.</p><p></p><p><strong>V3.6 External Resource Integrity</strong></p><p>Another common risk being addressed by this requirement is that of your JavaScript supply chain. Over the years we have seen so many attacks where a website is compromised not because they were breached, but because one of their dependencies was breached instead! Whilst this requirement focuses specifically on the venerable Subresource Integrity (SRI), at the time of writing we have a new feature in beta that will go far beyond the capabilities of SRI and provide even more value. Reach out if you're interested in joining the beta!</p><p></p><p><strong>3.6.1 (Level 3)</strong></p><blockquote>Verify that client‑side assets, such as JavaScript libraries, CSS, or web fonts, are only hosted externally (e.g., on a Content Delivery Network) if the resource is static and versioned and Subresource Integrity (SRI) is used to validate the integrity of the asset. If this is not possible, there should be a documented security decision to justify this for each resource.</blockquote><p></p><h4 id="getting-started">Getting Started</h4><p>If you want to start the process of improving the security of your web application, you can get started with a free trial on our website right now, or reach out to our team if you'd like some support through this process. Maybe you're looking to avoid the risk of a costly data breach in the future, maybe you have some particular compliance requirement you're trying to meet, or perhaps you've identified a specific concern you'd like to address. Either way, we're here to help, and we have more than a decade of experience to draw from as we support you.</p><p>To achieve this, the ASVS standard is a great basis to work from, and I want to quote the following objectives from the standard itself:</p><p></p><blockquote>•<strong> Use as a metric</strong> - Provide application developers and application owners with a yardstick with which to assess the degree of trust that can be placed in their Web applications,<br>•<strong> Use as guidance</strong> - Provide guidance to security control developers as to what to build into security controls in order to satisfy application security requirements, and<br>•<strong> Use during procurement</strong> - Provide a basis for specifying application security verification requirements in contracts.</blockquote><p></p><p>No matter which angle you're coming from, I'm hoping to see increased adoption of the OWASP ASVS requirements over time and the benefits that will bring to security across the Web.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀]]></title><description><![CDATA[<p>We’ve just passed a monumental milestone: <strong>2 trillion events processed</strong> through <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>!!! That’s <em>2,000,000,000,000</em> events for <a href="https://report-uri.com/products/content_security_policy?ref=scotthelme.ghost.io" rel="noreferrer">CSP</a>, <a href="https://report-uri.com/products/network_error_logging?ref=scotthelme.ghost.io" rel="noreferrer">NEL</a>, <a href="https://report-uri.com/products/dmarc_monitoring?ref=scotthelme.ghost.io" rel="noreferrer">DMARC</a>, and other browser-generated and email telemetry reports—ingested, parsed, and processed for our customers!</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image.png" class="kg-image" alt loading="lazy" width="565" height="215"></a></figure><p></p><p>This is a phenomenal milestone to achieve</p>]]></description><link>https://scotthelme.ghost.io/trillion-with-a-t-surpassing-2-trillion-events-processed/</link><guid isPermaLink="false">6863a159db89a500016be6a1</guid><category><![CDATA[Report URI]]></category><category><![CDATA[report-uri.com]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Wed, 02 Jul 2025 18:12:32 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀"><p>We’ve just passed a monumental milestone: <strong>2 trillion events processed</strong> through <a href="https://report-uri.com/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI</a>!!! That’s <em>2,000,000,000,000</em> events for <a href="https://report-uri.com/products/content_security_policy?ref=scotthelme.ghost.io" rel="noreferrer">CSP</a>, <a href="https://report-uri.com/products/network_error_logging?ref=scotthelme.ghost.io" rel="noreferrer">NEL</a>, <a href="https://report-uri.com/products/dmarc_monitoring?ref=scotthelme.ghost.io" rel="noreferrer">DMARC</a>, and other browser-generated and email telemetry reports—ingested, parsed, and processed for our customers!</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="565" height="215"></a></figure><p></p><p>This is a phenomenal milestone to achieve in the year that will mark our 10th Birthday, and shows just how quickly we're still growing!</p><p><strong>May 2015</strong>: 0 Events - Launch Day [<a href="https://scotthelme.co.uk/csp-and-hpkp-violation-reporting-with-report-uri-io/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]<br><strong>March 2021</strong>: 500,000,000,000 Events - 500 Billion Events [<a href="https://www.facebook.com/reporturi/posts/pfbid0jZJQRbPsDZZsKnA21QzwQa7CcATd1AjD8Zwr4EPHeqytrDAC5hTjuSc8wbH5xuz2l" rel="noreferrer">source</a>]<br><strong>February 2022</strong>: 1,000,000,000,000 Events - 1 Trillion Events [<a href="https://x.com/Scott_Helme/status/1490066996268052494?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]<br><strong>November 2023</strong>: 1,500,000,000,000 Events - 1.5 Trillion Events [<a href="https://scotthelme.co.uk/report-uri-a-week-in-numbers-2023-edition/?ref=scotthelme.ghost.io" rel="noreferrer">source</a>]<br><strong>July 2025</strong>: 2,000,000,000,000 Events - 2 Trillion Events [keep reading!]</p><p></p> <!--kg-card-begin: html--> <a href="https://report-uri.com/?ref=scotthelme.ghost.io" style="box-shadow: none;"> <img src="https://scotthelme.co.uk/content/images/2025/07/report-uri---Copy.png" style="max-width: 75%;" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀"> </a> <!--kg-card-end: html--> <p></p><p>To mark this incredible occasion, I'm excited to announce the <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">Report URI Global Telemetry Dashboard</a>. I often get questions about how many telemetry events we process per day, how many browsers send us telemetry, where does the telemetry come from?! Well, now, you can see the answer to all of those questions and a lot more. If you like cool graphs, geeky data, and insights into things operating at enormous scale, you're really going to enjoy this! 😎</p><p></p><h4 id="the-report-uri-global-telemetry-dashboard">The Report URI Global Telemetry Dashboard</h4><p>Taking screenshots and sharing them here really isn't going to do this justice, and there is a special surprise on one of the dashboards that I'm not going to spoil for you here! If you want to see all of this stuff live and have a play around with it yourself, head over to the <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">dashboard</a> now. For those of you still here, I want to share some information and insights into the data. </p><p>As of the time of writing, this is the exact data showing on the homepage of the dashboard:</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-1.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="782" height="717" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-1.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-1.png 782w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>That incredible number of almost 2 trillion events processed is right there, and a pretty incredible average of over <em>12,000 events per second</em> that we processed yesterday! The thing that always strikes me about this number is that this isn't requests that we're serving a response to, nothing here can be served from cache. Each inbound event needs processing, analysing, and storing, with the potential for alerts to be raised, Threat Intelligence to be generated, customers to be notified and much, much more. Another impressive statistic is just how many unique clients out there are sending us telemetry data, reaching almost a quarter of a billion unique clients in just the last 30 days! That's a huge amount of insight into the Web ecosystem and a significant contributing factor to the benefit we can bring by leveraging that insight to spot attacks faster. The remainder of the <a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io" rel="noreferrer">Dashboard Home</a> page shows additional insights into our traffic volumes, showing the hourly data for the last 24 hours, and the daily data for the last 7 and 30 days.</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-5.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="950" height="562" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-5.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-5.png 950w" sizes="(min-width: 720px) 720px"></a></figure><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-3.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="940" height="555" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-3.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-3.png 940w" sizes="(min-width: 720px) 720px"></a></figure><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/home/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-4.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="943" height="550" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-4.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-4.png 943w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>You can already start to see some interesting trends in that data, and these are trends that we've been tracking for a really long time. The 24 hour graph clearly shows the busy periods within a typical day for us, and it's not surprising to guess at what time of the day we see the most telemetry! Both the 7 day and 30 day graphs show our typical weekly trends too, with far more activity showing Mon-Fri than at the weekend. </p><p></p><h4 id="the-global-heat-map">The Global Heat Map</h4><p>This particular page in the dashboard is my second-favourite, and it's easy to see why. Looking at the telemetry we received over the last 30 days, this <a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io" rel="noreferrer">heat map</a> shows where that telemetry came from!</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-6.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1475" height="740" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-6.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-6.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-6.png 1475w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>You can zoom in to the map and really start to see where the busiest locations for us receiving telemetry are, with some expected locations showing up as the hottest areas of the map, and some more surprising areas showing up in there too! Zooming all the way in to the datacentre just down the road from me in Manchester, you can see exact counts of telemetry that went through that location in the last 30 days.</p><p></p><figure class="kg-card kg-image-card"><a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-7.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="748" height="825" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-7.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-7.png 748w" sizes="(min-width: 720px) 720px"></a></figure><p></p><p>Have a fly around the map and see what your nearest location is and what volume of telemetry went through that location in the last 30 days on our <a href="https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io" rel="noreferrer">Heat Map</a>!</p><p></p><h4 id="we-have-a-pew-pew-map">We have a Pew Pew Map!</h4><p>Okay okay I said I wasn't going to spoil the surprise, but I am way too excited about this one! Ever since seeing the original Norse Attack Map over 10 years ago, I've always wanted my own <a href="https://dash.report-uri.com/pewpewmap/?ref=scotthelme.ghost.io" rel="noreferrer">Pew Pew Map</a>, and now we have one! The GIF here is simply not going to do this justice, so you absolutely should head over to the page and see this thing live, in action, right now!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/pew-pew-map-1.gif" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1602" height="1015" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/pew-pew-map-1.gif 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/pew-pew-map-1.gif 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/07/pew-pew-map-1.gif 1600w, https://scotthelme.ghost.io/content/images/2025/07/pew-pew-map-1.gif 1602w" sizes="(min-width: 720px) 720px"></figure><p></p><p>What you're seeing here is <em>actual telemetry</em> being to sent to us! The map is 5 minutes behind real-time, and the data is real. The more red the zap is, the more telemetry we received from that location, and the faster the zap is moving, the more frequently we are receiving telemetry from that location! There are no estimations here, there is no mock data. This is a direct representation of real data, and I think that just makes this even more awesome! Our primary datacentre is located in SFO, as I'm sure you've guessed, and we do have a secondary location in AMS for our more regulated EU customers, but they are excluded from this data. </p><p>I'm tempted to get myself a second monitor off to the side of my desk just for this map, it really is quite cool to watch. If you come back at different times during the day, you can clearly see the different trends in our traffic patterns for inbound telemetry, to the point where you can almost track the Sun moving across the map based on the telemetry volumes!</p><p></p><h4 id="country-specific-data">Country Specific Data</h4><p>There's just no way you can top a Pew Pew Map, but we do still have a heap of interesting data that we can peruse. The next page is our <a href="https://dash.report-uri.com/country/?ref=scotthelme.ghost.io" rel="noreferrer">Country Specific Data</a>, which in many ways shows exactly what you might expect, with a couple of surprises in there too.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-8.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="850" height="493" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-8.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-8.png 850w" sizes="(min-width: 720px) 720px"></figure><p></p><p>The US is by far our largest source of telemetry data, which makes a lot of sense as a large portion of our customers are US-based companies, likely with US based visitors themselves. People may be surprised to see Japan so high up that list, but we do have a few quite notable customers in Japan, including a rather large coffee company, so they generate more than their fair share of telemetry. After that, the UK is coming in, followed by India that are helped by their population size, and then a broad selection of countries that you might expect to see up there too. The table below that gives exact details on our Top 20 Countries, but here are the top 3 for you.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-9.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="754" height="305" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-9.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-9.png 754w" sizes="(min-width: 720px) 720px"></figure><p></p><h4 id="some-technical-data">Some technical data!</h4><p>To close things out for now, we have our <a href="https://dash.report-uri.com/info/?ref=scotthelme.ghost.io" rel="noreferrer">Detailed Information</a> dashboard that goes into a little bit more of the technical data behind our inbound telemetry. We do have plans to expand this to incorporate a lot more metrics, but for now, I think we have a pretty good selection available. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-10.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1027" height="1153" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-10.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-10.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-10.png 1027w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Looking at the HTTP Version and TLS Protocol used, you can see that we clearly have a very wide selection of modern clients out there, using TLSv1.3 almost exclusively, and HTTP/2 and HTTP/3 taking the lion's share of the application protocol. When responding to inbound telemetry requests, our most common response is an empty txt response to save as much bandwidth and overhead as possible, we simply reply with an empty 201 to acknowledge receipt. After the 201, we have a variety of response codes that we might use along with a message in the body to give more information on the problem to help with debugging. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/07/image-11.png" class="kg-image" alt="Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀" loading="lazy" width="1025" height="1145" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-11.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-11.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-11.png 1025w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Taking a look across the next set of data, I'm often quite surprised to see Firefox so heavily represented in the Client UA, but there are a wider selection of Chromium based browsers in the mix. The front runners for Top Colo By Requests won't be a huge surprise given the country data above, but it is nice to see a breakdown of where our most popular locations are. Closing it out, we have a little more client data showing the Client OS, with a probably expected Windows and iOS being the most dominant, and Client Type showing that Desktop is hanging on to the top spot with Mobile close behind!</p><p></p><h4 id="theres-more-to-come">There's more to come!</h4><p>I created this dashboard to scratch an itch, I got to play with some cool data, and as I mentioned above, I've <em>always</em> wanted a pew pew map! I also really enjoyed stepping away from the more serious responsibilities of being 'CEO and Founder' to play around with cool technology and build something awesome! That is, after all, why I founded the company in the first place. </p><p>I hope the dashboard will be interesting and provide some insight into what we're doing, and if there's something you'd like to see added to the dashboard, or any questions you have, just let me know in the comments below. As I come across any interesting metrics in the future, I'll be sure to add them to the dashboard, and I'd love to hear your suggestions too!</p><p></p>]]></content:encoded></item><item><title><![CDATA[V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!]]></title><description><![CDATA[<p>In my <a href="https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">first blog post about hacking my Tesla Powerwalls</a>, I laid out all of the foundations and information about my home energy setup. You really need to read that blog post first as I'm going to be building on all of that work here, and assuming that</p>]]></description><link>https://scotthelme.ghost.io/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/</link><guid isPermaLink="false">6849e889cca6290001a6e931</guid><category><![CDATA[Home Assistant]]></category><category><![CDATA[Teslemetry]]></category><category><![CDATA[Tesla Powerwall]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Fri, 13 Jun 2025 10:52:02 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!"><p>In my <a href="https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">first blog post about hacking my Tesla Powerwalls</a>, I laid out all of the foundations and information about my home energy setup. You really need to read that blog post first as I'm going to be building on all of that work here, and assuming that you're familiar with everything in that post.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/image.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="432" height="132"></figure><p></p><h4 id="the-final-piece-of-the-puzzle">The final piece of the puzzle</h4><p>In my previous post, I had focused on solving a particular problem. My Powerwalls would charge up overnight on the very cheap off-peak rate so that they could run my house during the day, avoiding the expensive peak rate. The problem I was coming across was that the batteries would hit 100% SoC overnight, start depleting during the day, but when the sun started shining, our solar array could fill the batteries back up to 100% SoC and then export excess solar production at an almost worthless cost. I created an automation to stop the batteries from charging once they hit 75% SoC, which left plenty of room for excess solar production to fill up the batteries during the day rather than exporting the energy. This was an improvement, but it wasn't an ideal solution, so I set out to resolve it. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/OE-Logo-RGB_Unstacked-white-xl-octopus--1--min-1.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="350" height="207"></figure><p></p><h4 id="my-new-export-tariff">My new export tariff</h4><p>I've finally managed to get myself on a decent export tariff, meaning that I'm now being paid much more for energy that I export to the grid. I feel that energy I export to the grid should be paid for at the same rate as energy I import from the grid, but that's unfortunately not the case. I am, however, now being paid almost <em>4x more</em> than I was before for energy export, and it still falls 50% short of what my import costs! My import tariff is still the same, and my export tariff is now much better.</p><p></p><table> <thead> <tr> <th>Activity</th> <th>Time</th> <th>Cost</th> </tr> </thead> <tbody> <tr> <td>Import</td> <td>05:30 to 23:30</td> <td>£0.28/kWh</td> </tr> <tr> <td>Import</td> <td>23:30 to 05:30</td> <td>£0.07/kWh</td> </tr> <tr> <td>Export</td> <td>00:00 to 23:59</td> <td><s>£0.04/kWh</s> <em><strong>£0.15/kWh</strong></em></td> </tr> </tbody> </table> <p></p><p>Of course, the fact that I can purchase electricity for £0.07/kWh overnight and then export it for £0.15/kWh is quite an attractive proposition, and something that I'd like to take advantage of. This is something known as load-shifting, or <a href="https://en.wikipedia.org/wiki/Load_management?ref=scotthelme.ghost.io" rel="noreferrer">Load Management</a>, where suppliers want to encourage people to move their usage into more desirable time periods, and is the reason for the very existence of the cheap overnight tariffs in the first place. I already have the batteries and solar array to allow me to do this, so all I needed was a solution to automate it.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/image-4.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="521" height="259"></figure><p></p><h4 id="using-home-assistant-and-teslemetry-again">Using Home Assistant and Teslemetry, again!</h4><p>In my previous post, I used <a href="https://teslemetry.com/?ref=scotthelme.ghost.io" rel="noreferrer">Teslemetry</a> to control when the batteries could charge from the grid to introduce the ceiling of 75% SoC to leave room for excess solar production during the day. Now, though, I want to go one step further and charge the batteries to 100% SoC overnight, and slowly let them feed back to the grid during the day if we have excess capacity, along with excess solar production, to leave me with enough power to get back to the cheap rate at 23:30 again.</p><p></p><p>The <a href="https://www.home-assistant.io/?ref=scotthelme.ghost.io" rel="noreferrer">Home Assistant</a> automation to do this was surprisingly simple, so I will post the whole thing here, and then we can talk through what it does.</p><p></p><pre><code class="language-yaml">alias: Dynamic Battery Export Control description: >- Adjust export setting and export mode display based on battery SoC and time-of-day triggers: - minutes: /15 trigger: time_pattern actions: - variables: now_ts: "{{ now().timestamp() }}" today: "{{ now().date().isoformat() }}" start_ts: "{{ (today ~ 'T05:30:00') | as_datetime | as_timestamp }}" end_ts: "{{ (today ~ 'T23:30:00') | as_datetime | as_timestamp }}" end_for_target_ts: "{{ (today ~ 'T23:15:00') | as_datetime | as_timestamp }}" current_soc: "{{ states('sensor.shireburn_charge') | float(0) }}" - choose: - conditions: - condition: template value_template: "{{ now_ts < start_ts or now_ts > end_ts }}" sequence: - target: entity_id: input_number.target_soc data: value: 100 action: input_number.set_value - target: entity_id: input_text.export_mode_state data: value: No Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'never' }}" - target: entity_id: select.shireburn_allow_export data: option: never action: select.select_option default: - variables: total_secs: "{{ end_for_target_ts - start_ts }}" elapsed_secs: "{{ now_ts - start_ts }}" progress: >- {{ (elapsed_secs / total_secs) if elapsed_secs < total_secs else 1.0 }} target_soc: "{{ 95 - (85 * (progress ** 1.5)) }}" - target: entity_id: input_number.target_soc data: value: "{{ target_soc | round(0) }}" action: input_number.set_value - choose: - conditions: - condition: template value_template: | {% set hour = now().hour %} {% if hour < 23 %} {{ current_soc > (target_soc + 5) }} {% else %} {{ current_soc > target_soc }} {% endif %} sequence: - target: entity_id: input_text.export_mode_state data: value: Battery + Solar Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'battery_ok' }}" - target: entity_id: select.shireburn_allow_export data: option: battery_ok action: select.select_option - conditions: - condition: template value_template: "{{ current_soc < (target_soc - 5) }}" sequence: - target: entity_id: input_text.export_mode_state data: value: No Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'never' }}" - target: entity_id: select.shireburn_allow_export data: option: never action: select.select_option default: - target: entity_id: input_text.export_mode_state data: value: Solar Export action: input_text.set_value - condition: template value_template: "{{ states('select.shireburn_allow_export') != 'pv_only' }}" - target: entity_id: select.shireburn_allow_export data: option: pv_only action: select.select_option mode: single </code></pre><p></p><p></p><p>The automation runs every 15 minutes and first checks if we're in the off-peak window of 23:30 to 05:30, and if we are, it sets the Target SoC to 100% and that's it, we want the battery charging. If we're in the peak window of 05:30 to 23:30, then a few things happen.</p><p>The first thing it does is calculate how far through the peak window we are, which is used to calculate the Target SoC. Originally, I had a linear decline throughout the day which seemed like it should work just fine. If we were 25% of the way through the window, the Target SoC would be 75%, at 50% of the way through the window the Target SoC was 50%, and 75% of the way through the window, the Target SoC would be 25%.</p><p>Plotting that out it looks like the graph below, and it did work fine, but we had some evenings where our usage would spike quite high and there wasn't enough of a buffer left in the battery, leaving us short on battery power. I didn't want to have to worry about problems like this, and whilst it worked fine for the most part, it needed improving.</p><p></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/06/Screenshot-2025-06-11-213247.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="478" height="340"><figcaption><span style="white-space: pre-wrap;">Target SoC with a linear decline</span></figcaption></figure><p></p><p>I changed the Target SoC to an exponential decline throughout the day, meaning it would hold a slightly higher SoC during the day, but then tail off quite hard at the end of the day, making the Target SoC curve look like this. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://scotthelme.ghost.io/content/images/2025/06/Screenshot-2025-06-11-213303.png" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="480" height="340"><figcaption><span style="white-space: pre-wrap;">Target SoC with an exponential decline</span></figcaption></figure><p></p><p>This gave us much more breathing room if we had a sudden increase in usage during the evening, and if not, the batteries could just export at a more aggressive rate to reach the lower Target SoC. This worked much better for us, gave us the breathing room we needed, but still meant that we could export any spare capacity during the peak window.</p><p>Now that the Target SoC is established, we can decide on the export mode to keep the Actual SoC aligned with the Target SoC. For that, there are the following 3 options.</p><p></p><p><strong>Condition 1:</strong> Battery SoC > (Target SoC + 5) = Export Battery + Solar<br><strong>Condition 2:</strong> Battery SoC < (Target SoC - 5) = Export Nothing<br><strong>Default:</strong> Export Solar Only<br><br></p><p>Condition 1: If we're more than 5% above target, we should export from the solar array and the batteries as we have spare capacity in the batteries. This will bring the Actual SoC down to the Target SoC faster as time goes by.</p><p>Condition 2: If we're more than 5% below target we should export nothing, as we're trying to preserve the battery capacity by using solar to power the house as much as possible. This reduces the load on the battery and allows the Target SoC to fall over time while holding the Actual SoC as high as possible.</p><p>Default: The aim here is to run the house on the batteries and export solar production to the grid, trying to maintain a steady decline in Actual SoC throughout the day that should align it with Target SoC.</p><p>If you look at the graphs above for Actual SoC, you can see the step changes every 15 minutes when the automation runs and changes the export mode to adjust the Actual SoC as needed, keeping things well aligned! One thing I am keeping in mind is that we're in summer right now, so our solar production is quite good and our energy usage is probably on the lower end of what's typical. As we approach autumn and then winter, I'm wondering if I might need to make the Target SoC curve a little steeper, to hold a higher charge during the day and tail off more aggressively in the evening, to give us a little more breathing room. Only time will tell on that one, but if there's a change required, I will come back and update this post with a note.</p><p></p><h4 id="seeing-it-in-action">Seeing it in action</h4><p>Now that I've run this automation for over a week, it's had a chance to run through days where we have exceptionally low solar production (British Summer), and we export very little, to days like yesterday where we have amazing solar production all day (one day in British Summer), and as a result, we see some great export rates too!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6398-1.jpeg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="600" height="979" srcset="https://scotthelme.ghost.io/content/images/2025/06/IMG_6398-1.jpeg 600w"></figure><p></p><p>The top graph shows our export for the day and you can clearly see the Solar Bell (the solar production bell curve) from sunrise to sunset. Dotted throughout the day you can also see the occasional spikes in our export, where the automation has detected excess capacity in the batteries and released it into the grid, causing a spike in our 30 minute export window. You can then clearly see the exponential decline for battery capacity kicking in as we release excess battery capacity late into the evening. The bottom graph then shows our import, which looks very different, hauling in that cheap power overnight and not importing any energy during the day. If you want to see those graphs as kWh instead of cost, here they are.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6399-1.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="600" height="966" srcset="https://scotthelme.ghost.io/content/images/2025/06/IMG_6399-1.jpg 600w"></figure><p></p><h4 id="maximum-import-maximum-export">Maximum import, maximum export</h4><p>Once we hit the cheap tariff at 23:30, the heavy import begins. Not only do our Powerwalls begin charging, capable of pulling in a total of 15kW, we have quite a few other systems that make use of the off-peak rate too. If either one of our two EVs are plugged in, they will begin charging at 7kW, the hot tub only heats and filters the water overnight, so that fires up using almost 4kW, and a variety of smaller appliance like the tumble dryer might be waiting too, using a few more kW of power.</p><p>This means that we can quite easily hit our maximum import limit of 24kW, at which point the Powerwalls will automatically throttle their import to prevent us from exceeding the limit and blowing our 100A main fuse (100A x 240v = 24,000W). Given that we have a 24kW import limit, you'd think that we have a 24kW export limit too, but, we don't... For reasons known only to them, our Distribution Network Operator determined that we are only able to export at a maximum rate of 3.869kW, which you can see on the negative portion of the graph during the day where we hit the flat bottom of our maximum export rate. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6400.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1290" height="764" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/IMG_6400.jpg 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/IMG_6400.jpg 1000w, https://scotthelme.ghost.io/content/images/2025/06/IMG_6400.jpg 1290w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Side note: Yes, I know our nominal voltage on the grid in the UK is 230v after we harmonised with Europe, but in reality, it's often much higher. My 100A fuse is also BS1361 rated, meaning it can realistically carry 120A indefinitely, and last for prolonged periods at 125A. Even at our minimum tolerance on grid voltage of 207v, we'd still only be pulling 116A at 24kW (24,000w / 207v = 116A). Here's the grid voltage for the same time window shown in the above graph to show what I mean.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/06/IMG_6401.jpg" class="kg-image" alt="V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1290" height="734" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/06/IMG_6401.jpg 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/IMG_6401.jpg 1000w, https://scotthelme.ghost.io/content/images/2025/06/IMG_6401.jpg 1290w" sizes="(min-width: 720px) 720px"></figure><p></p><p>It is interesting to see what effect solar PV has on the peak voltage, getting us quite high at 255v, and then at the opposite end of the scale under heavy load, we can dip all the way down to 233v!</p><p></p><h4 id="can-we-control-the-powerwall-charge-rate">Can we control the Powerwall charge rate?</h4><p>Just before we wrap up, there is one final thing that I haven't yet found a solution for that I would like to resolve, so if anyone has any ideas, please let me know in the comments. When the Powerwalls start charging from the grid, they charge at their absolute maximum possible rate of 5kW per battery, giving us our total import rate of 15kW into our 3 batteries. Each battery has a usable capacity of 13.5kWh, so, ignoring efficiency for easy maths, at maximum charging rate they can go from 0% to 100% in 2 hours and 45 minutes.</p><p>It seems unnecessary to charge at their absolute maximum rate which will have them sat at 100% SoC for more than half of our off-peak window, and have charged them at full power when it wasn't needed. I'd much prefer to be able to set them to charge at 2.5kW per battery, which will still allow them to go from 0% to 100% during our off-peak window. Charging them more slowly is better for the batteries in the long run due to the lower charge rate, and, they will spend less time at 100% SoC which is also better for their health. If I had dynamic control of the charge rate I could even calculate what rate was required for them to hit 100% SoC right as the off-peak window ends! </p><p>At present, I can't see any way to do this, either setting a static import limit per battery or a dynamic limit based on SoC and time remaining. I know I can restrict the overall site import limit with the Tesla One app, but that's not useful here as it isn't flexible and could result in no import into the batteries if something else is using the import! I'm a little stuck on this one, do you have any ideas?</p><p></p><p></p> <!--kg-card-begin: html--> <style> pre[class*="language-"], code[class*="language-"] { font-size: 0.85em !important; } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer"> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js" integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js" integrity="sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <!--kg-card-end: html--> ]]></content:encoded></item><item><title><![CDATA[Shorter certificates are coming!]]></title><description><![CDATA[<p>Well, I was certainly hoping for this result, but wasn't necessarily expecting it! I'm pleased to report that Ballot SC-081v3 passed, and that shorter certificate lifetimes are now coming!</p><p></p><h4 id="the-schedule">The Schedule</h4><p>I will go into more detail later in the post, but right now, let'</p>]]></description><link>https://scotthelme.ghost.io/shorter-certificates-are-coming/</link><guid isPermaLink="false">6807855b51addd000148f995</guid><category><![CDATA[TLS]]></category><category><![CDATA[PKI]]></category><category><![CDATA[Certificate Authorities]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Tue, 22 Apr 2025 13:37:29 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp" alt="Shorter certificates are coming!"><p>Well, I was certainly hoping for this result, but wasn't necessarily expecting it! I'm pleased to report that Ballot SC-081v3 passed, and that shorter certificate lifetimes are now coming!</p><p></p><h4 id="the-schedule">The Schedule</h4><p>I will go into more detail later in the post, but right now, let's cover the important details! Here is the schedule that was proposed and voted on, and will now come into effect:</p><p></p><table> <thead> <tr> <th><strong>Certificate issued on or after</strong></th> <th><strong>Certificate issued before</strong></th> <th><strong>Maximum Validity Period</strong></th> </tr> </thead> <tbody> <tr> <td></td> <td>March 15, 2026</td> <td>398 days</td> </tr> <tr> <td>March 15, 2026</td> <td>March 15, 2027</td> <td>200 days</td> </tr> <tr> <td>March 15, 2027</td> <td>March 15, 2029</td> <td>100 days</td> </tr> <tr> <td>March 15, 2029</td> <td></td> <td>47 days</td> </tr> </tbody> </table> <p></p><p>This means that the important dates are as follows:</p><p><strong>March 15th 2026</strong>: <em>All new certificates capped at 200 days validity</em></p><p><strong>March 15th 2027</strong>: <em>All new certificates capped at 100 days validity</em></p><p><strong>March 15th 2029</strong>: <em>All new certificates capped at 47 days validity!</em></p><p></p><p>It's a little sad that the dates slipped since the initial proposal of this ballot, hence the 'v3', but still, we now have a definitive date in the future when we know these things will be happening.</p><p>If we look at the progress of shortening certificates over time, we can see that the trend fits well, and if anything, our progress had stalled out a little, but we're now back on track, even if at a reduced pace.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/validity-period-final-data.png" class="kg-image" alt="Shorter certificates are coming!" loading="lazy" width="1227" height="715" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/validity-period-final-data.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/validity-period-final-data.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/validity-period-final-data.png 1227w" sizes="(min-width: 720px) 720px"></figure><p></p><p>You can see my views on the progress of this ballot in my earlier post, <a href="https://scotthelme.co.uk/are-shorter-certificates-finally-coming/?ref=scotthelme.ghost.io" rel="noreferrer">Are shorter certificates finally coming?!</a>.</p><p></p><h4 id="who-voted-for-what">Who voted for what?</h4><p>As the CA/B Forum votes are public, we can take a look at who voted for what, and in the past, that has given us some interesting insight into the sentiment across industry. If you want to view the official announcement, you can do that <a href="https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/9768xgUUfhQ?ref=scotthelme.ghost.io" rel="noreferrer">here</a>, but here are the results.</p><p></p><blockquote>The voting period for <strong>SC-081v3: Introduce Schedule of Reducing Validity and Data Reuse Periods</strong> has completed. The ballot has: <strong>PASSED<br><br>Voting Results<br><br>Certificate Issuers</strong><br><br>30 votes in total:<br> * 25 voting YES: Amazon, Asseco Data Systems SA (Certum), Buypass AS, Certigna (DHIMYOTIS), Certinomis, DigiCert, Disig, D-TRUST, eMudhra, Fastly, GlobalSign, GoDaddy, HARICA, iTrusChina, Izenpe, NAVER Cloud Trust Services, OISTE Foundation, Sectigo, SHECA, SSL.com, SwissSign, Telia Company, TrustAsia, VikingCloud, Visa<br> * 0 voting NO:<br> * 5 ABSTAIN: Entrust, IdenTrust, Japan Registry Services, SECOM Trust Systems, TWCA<br><br><strong>Certificate Consumers</strong><br><br>4 votes in total:<br> * 4 voting YES: Apple, Google, Microsoft, Mozilla<br> * 0 voting NO:<br> * 0 ABSTAIN:</blockquote><p></p><p>That is absolutely phenomenal support, and I think largely due to the open nature of the proposal, seeking to gather industry feedback and adapting the proposal based on that feedback. It's a little disappointing to see CAs abstain from voting, especially IdenTrust, but here are the responses from those CAs who abstained and provided a <a href="https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/bvWh5RN6tYI?ref=scotthelme.ghost.io" rel="noreferrer">reason</a>:</p><p></p><blockquote>IdenTrust abstains from voting on Ballots SC-081v3.<br><br>While we agree with all proposals related to TLS DV validation and support full automation, we remain unconvinced of the security benefits of further reducing the validity period for OV and EV TLS certificates. </blockquote><p></p><p>It's interesting that the opposition was not to reducing the validity of DV certificates, which can be easily automated, but to OV and EV certificates instead. It's widely known what my views on OV and EV certificates are ([<a href="https://scotthelme.co.uk/gone-for-ever/?ref=scotthelme.ghost.io" rel="noreferrer">1</a>][<a href="https://scotthelme.co.uk/are-ev-certificates-worth-the-paper-theyre-written-on/?ref=scotthelme.ghost.io" rel="noreferrer">2</a>][<a href="https://scotthelme.co.uk/looks-like-a-duck-swims-like-a-duck-qwacs-like-a-duck-probably-an-ev-certifiacate/?ref=scotthelme.ghost.io" rel="noreferrer">3</a>] etc...), but if we're assuming that those certificates have too much manual process involved, and thus require human intervention and are inherently unfriendly to automation, it's really just another nail in the coffin.</p><p></p><blockquote>JPRS abstains from voting on Ballot SC-081.<br><br>We agree with the value of reducing certificate validity periods.<br>We also believe that this ballot indicates to subscribers the inevitability of automation and represents a significant step forward for the WebPKI ecosystem.<br><br>However, we will abstain from this ballot.<br>While we will do our best to support our subscribers and hope that subscribers and other stakeholders in the WebPKI ecosystem will be able to follow the proposed timeline, it is currently uncertain whether it can realistically be achieved.<br><br>In addition, we hope that discussions will take place even after this ballot when necessary, taking into account the actual state of migration in the WebPKI ecosystem.</blockquote><p></p><p>It's good to see JPRS acknowledging the enormous benefit that this ballot brings, and I can also understand and appreciate the hesitation around the ecosystem being ready. Whilst adoption of automated solutions for certificate management has been going well, it's not been going well enough. With that said, there has also never been a requirement for people to adopt those solutions, it was simply a good idea to do so. Now that there is a requirement to have these solutions in place, and a generous 4 year window to get them in place, I'm hoping we start to see an increase in the rate of adoption and progress. </p><p></p><blockquote>TWCA "ABSTAINS" from voting on ballot SC-081v3.<br><br>1. We agree that shortening the certificate validity period can reduce cybersecurity risks, and we also acknowledge the advantages of automated certificate management.<br><br>2. We believe that reducing the validity period from 100 days to 47 days is too drastic. Compared to the harm caused by phishing websites, there is currently no way to demonstrate to users how high the cybersecurity risk would be if the certificate validity period were 100 days.<br><br>3. For some websites that already use certificates, if they ultimately fail to deploy automated certificate management, they might abandon HTTPS in favor of HTTP (given that in today’s browser UIs, HTTP only displays a red warning in the upper left corner, whereas improper management leading to certificate expiration results in warnings across the entire page).</blockquote><p></p><p>Again, it's great to see a CA acknowledge the value that these shorter certificates will bring, but after this, I start to struggle with this response. It's really not clear to me what point is being made in comment #2, but I'm not sure what relevance phishing websites have here, and I don't know what cybersecurity risk is present if a certificate is only valid for 100 days! For comment #3, I completely disagree. There's absolutely no reasonable concern that websites are going to move back to HTTP from HTTPS. </p><p></p><blockquote>SECOM Trust Systems ABSTAINS from voting on Ballot SC-081v3.<br><br>We agree with the value of shortening certificate validity periods, and we support this direction in the long term.<br><br>In addition, we believe this ballot signals to subscribers that automation is inevitable, and that this is a great step forward for the WebPKI ecosystem.<br><br>On the other hand, we are also aware that some subscribers may experience costs to adapt.<br><br>In our business practice, we think it is important to be able to explain not only the value but also the cost and explain that our approach is net-positive for the web, unless there is a reasonable alternative.<br><br>Assuming a tradeoff between the period to prepare and the cost, we do not have an answer to shortening it to 47 days instead of 90 days.<br><br>Therefore, we abstain.</blockquote><p></p><p>Lastly, SECOM are another CA acknowledging the enormous benefits that this proposal will bring, but I'm struggling to agree with their argument. It seems that they are suggesting that if certificates were valid for 90 days, then subscribers could/would renew them manually, but a validity period of 47 days would be too short for manual renewal to be practical. I'd make the argument that even at 90 days validity, manual renewal is completely impractical and should already be fully automated, so this doesn't make any real difference at all.</p><p></p><h4 id="come-and-geek-out-with-me">Come and geek out with me</h4><p>If you really want to know more about this whole TLS/PKI ecosystem, why these changes are coming, and the years of history that are driving this, I'd really recommend you check out our training course:</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image-4.png" class="kg-image" alt="Shorter certificates are coming!" loading="lazy" width="1012" height="466" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/image-4.png 1012w" sizes="(min-width: 720px) 720px"></figure><p></p><p>There are full details of what we cover on the <a href="https://www.feistyduck.com/training/practical-tls-and-pki?ref=scotthelme.co.uk" rel="noreferrer">training page</a>, but if you want to get hands on with TLS and PKI, fully deploy HTTPS to your own server, including getting a real certificate, build your own CA, and so much more, you should really get involved and join us! It's a fairly intermediate level course, I probably wouldn't recommend it for beginners, but it contains everything you're going to need from a practical perspective, and we have plenty of opportunity to talk about and understand current affairs too.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Hacking my Tesla Powerwalls to be the ultimate home energy solution!]]></title><description><![CDATA[<p>I've had solar and batteries at home for quite some time now, and despite my experience with them being really awesome, there were a few little things that were bugging me. Using systems from various different suppliers doesn't always provide the perfect integration, so I hacked</p>]]></description><link>https://scotthelme.ghost.io/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/</link><guid isPermaLink="false">68021b1ded448d0001f0d432</guid><category><![CDATA[Home Assistant]]></category><category><![CDATA[Tesla Powerwall]]></category><category><![CDATA[Teslemetry]]></category><dc:creator><![CDATA[Scott Helme]]></dc:creator><pubDate>Mon, 21 Apr 2025 11:58:28 GMT</pubDate><media:content url="https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp" medium="image"/><content:encoded><![CDATA[<img src="https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!"><p>I've had solar and batteries at home for quite some time now, and despite my experience with them being really awesome, there were a few little things that were bugging me. Using systems from various different suppliers doesn't always provide the perfect integration, so I hacked together my own!</p><p></p><h4 id="no-not-that-kind-of-hacking">No, not that kind of hacking!</h4><p>I have done that kind of hacking over the years, and many times I've blogged about it right here, from <a href="https://scotthelme.co.uk/the-vulnerable-web-api-for-my-nissan-leaf/?ref=scotthelme.ghost.io" rel="noreferrer">cars</a> to <a href="https://scotthelme.co.uk/hacking-iot-cameras/?ref=scotthelme.ghost.io" rel="noreferrer">cctv cameras</a> and <a href="https://scotthelme.co.uk/ee-brightbox-router-hacked/?ref=scotthelme.ghost.io" rel="noreferrer">routers</a> to <a href="https://scotthelme.co.uk/hotel-hippo-insecure/?ref=scotthelme.ghost.io" rel="noreferrer">hotel websites</a>, but this blog post isn't about that type of hacking, it's about what I consider to be the more traditional definition of the term. This is the type of hacking where you get something to perform a function that it wasn't intended to perform, where you take something and make it better than it was by tinkering with it. Think more 'life hack' than 'computer hack', and you're heading along the right lines here. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="432" height="132"></figure><p></p><h4 id="the-problem">The problem</h4><p>I have a few different system at home that all are related to power consumption, and whilst they are all 'smart', they don't fully integrate. Here's the overview:</p><p></p><ul><li>☀️Solar Panels: We have 14 Perlight solar panels managed by Enphase that make up the 4.2kWp array on our roof, and they produce energy when the sun shines. This part is pretty straightforward!</li><li>🔋Tesla Powerwalls: We have 3x Tesla Powerwall 2 in our garage that were purchased to help us load-shift our energy usage. Electricity is very expensive in the UK and moving from peak usage which is 05:30 to 23:30 at £0.27/kWh, to off-peak usage, which is 23:30 - 05:30 at £0.07/kWh, is a significant cost saving.</li><li>💡Smart Tariff: My wife and I both drive electric cars and our electricity provider, Octopus Energy, has a Smart Charging tariff. If we plug in one of our cars, and cheap electricity is available, they will activate the charger and allow us to use the off-peak rate, even at peak times. </li></ul><p></p><p>All of this is great, but it does present a few issues:</p><p>1) The main issue is that the Powerwalls will always fill up to 100% on the off-peak rate overnight, aiming to maximise how much power they can supply us during the peak-rate, load shifting the maximum possible amount and saving the most money. That's great, but it doesn't factor in that we have a 4.2kWp solar array on our roof. Once the sun rises and we start generating solar power, the batteries are still full, or almost full, and we quickly begin exporting energy to the grid. Our export tariffs in the UK are terrible and our supplier only pays us a measley £0.04/kWh for exported energy, which is a complete waste! I want a way to tell the Powerwalls to only charge to, let's say, 80% on grid power, and leave headroom for solar generation during the day. No such feature exists. </p><p></p><p>2) The next issue is that the Powerwalls know about our cheap off-peak tariff overnight from 23:30 to 05:30, but not the flexibility of our Smart Charging tariff during peak times. That means that if we plug one of our cars in during the day to top them up, the Powerwalls think we're on peak rates and will charge the car from the batteries and not the grid. I'd like the Powerwalls to be able to respond to our current energy price, rather than a fixed time schedule. No such feature exists. </p><p></p><p>3) The final issue is similar to the above, but about our Powerwall batteries rather than the car batteries. If we have a window of cheap electricity from our electricity supplier, then the Powerwalls should top themselves up when rates are cheap. Not only does this feature not exist, but it also needs to factor in point #1 about not overfilling the batteries and leaving room for solar generation. Argh!</p><p></p><h4 id="home-assistant-and-teslemetry-to-the-rescue">Home Assistant and Teslemetry to the rescue!</h4><p>I've talked about Home Assistant a <em>lot</em> and we use it extensively throughout our house. From lights to appliances, monitoring, alarm systems and more, Home Assistant has a finger in almost every pie! The Tesla Powerwalls integrate nicely with Home Assistant and provide all of their information which I've formulated into a nice dashboard. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="1080" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>I can keep a track of the State of Charge (SoC), power coming in and out of the batteries, power coming in and out of the grid, and much more!</p><p></p><p>Of course, we also have an integration for our solar array too, and the Enphase integration allows us to see exactly what's going on with our solar array, and all of the statistics you'd ever want. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="1026" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Finally, our energy supplier has a pretty cool API, and there's an integration for that in Home Assistant too! We can see a whole bunch of different data about our gas and electricity supply, and, crucially, the current rate we're being charged for electricity!</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="913" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>So, I now have all of the information that I could possibly need in one place, but there was one final piece of the puzzle to solve. The Tesla Powerwall integration in Home Assistant does not allow for control over the Tesla Gateway or Tesla Powerwall devices themselves, it simply retrieves information. No problem, though, because after some quick Googling, I found the <a href="https://developer.tesla.com/?ref=scotthelme.ghost.io" rel="noreferrer">Tesla Fleet API</a>. </p><blockquote>Fleet API is a data and command service providing access to Tesla vehicles and energy devices. Developers can interact with their own devices, or devices for which they have been granted access by a customer.</blockquote><p></p><p>The Fleet API would allow me to have the level of control over my Powerwalls that I needed, and there are a bunch of API clients out there that I could get up and running and build something from. Rather than getting knee deep in building something when my time is a little tight right now, I thought I'd take a look at what solutions exist already, and I quickly found Teslemetry.</p><p></p><p></p> <!--kg-card-begin: html--> <img src="https://scotthelme.co.uk/content/images/2025/04/teslemetry.svg" class="kg-image" style="max-width:400px;" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!"> <!--kg-card-end: html--> <p></p><p></p><h4 id="using-teslemetry">Using Teslemetry</h4><p>The Teslemetry <a href="https://teslemetry.com/pricing?ref=scotthelme.ghost.io" rel="noreferrer">subscription</a> is super cheap, at £2.26/mo including taxes, and it came with a free trial period, and a Home Assistant integration, so it seemed like a winner. At this price it's really not worth spending any time on writing my own code, and I can support the service if it provides a benefit. Getting it set up was a breeze, logging in to my Tesla account and granting it access to only the Powerwalls and not the car, and then hooking that up to the Home Assistant integration. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1920" height="913" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1920w" sizes="(min-width: 720px) 720px"></figure><p></p><p>As you can see, this is no longer just information coming from the Powerwalls, I now have the ability to control settings on the Powerwalls too! This is what's going to help me create the automations that I need to ensure the Powerwalls behave how I want them to behave.</p><p></p><h4 id="capping-the-battery-power-when-grid-charging">Capping the battery power when grid charging!</h4><p>The first automation I wanted to create was to limit how high the batteries will charge when charging from the grid. The primary benefit here would be to limit the SoC on the overnight charge, so that once the solar starts generating, there would be room in the battery to store than, rather than export it to the grid. There may also be another benefit here in preventing the batteries from hitting 100% SoC and then sitting at 100% SoC for a prolonged period of time, which can be bad for their health over the long term. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1600" height="908" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 1600w" sizes="(min-width: 720px) 720px"></figure><p></p><p>This automation is set to run every 30 minutes, at the top and bottom of the hour, as that's the time window that my electricity rate can change. Here's the YAML for the automation:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Disable Grid Charging description: "" triggers: - trigger: time_pattern minutes: /30 conditions: - condition: state entity_id: switch.shireburn_allow_charging_from_grid state: "on" - condition: or conditions: - condition: numeric_state entity_id: >- sensor.octopus_energy_electricity_1_snip_3_current_rate above: 0.11 - condition: numeric_state entity_id: sensor.shireburn_charge above: 75 actions: - action: switch.turn_off metadata: {} data: {} target: entity_id: switch.shireburn_allow_charging_from_grid mode: single </code></pre><p></p><p>So, every 30 minutes, I'm checking to see if the cost of electricity is high, or the battery is above 75% SoC, and if so, we're disabling grid charging. This means the battery will only charge when electricity is cheap and the SoC is below the target of 75%. In these summer months where we are producing plenty of solar power, I can keep the target SoC on grid charging at 75%, leaving plenty of room to store solar generation, and in the winter months, I will likely set the target SoC back to 100% as we will have little solar generation and will need all of the battery backup we can get. </p><p>The automation to enable grid charging is then of course very similar, checking for energy cost and SoC target, and then enabling grid charging when required.</p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1597" height="912" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 1597w" sizes="(min-width: 720px) 720px"></figure><p></p><p>And here's the YAML for that one too:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Enable Grid Charging description: "" triggers: - trigger: time_pattern minutes: /30 conditions: - condition: state entity_id: switch.shireburn_allow_charging_from_grid state: "off" - condition: numeric_state entity_id: sensor.shireburn_charge below: 75 - condition: or conditions: - condition: time after: "23:30:00" before: "05:30:00" weekday: - mon - tue - wed - thu - fri - sat - sun - condition: numeric_state entity_id: >- sensor.octopus_energy_electricity_1_snip_3_current_rate below: 0.11 enabled: true actions: - action: switch.turn_on metadata: {} data: {} target: entity_id: switch.shireburn_allow_charging_from_grid mode: single </code></pre><p></p><p>This has worked out really well and is doing exactly what we need it to do. If we look at the SoC on the batteries over the last 30 days, you can see when I enabled this automation as they're no longer hitting 100% SoC on the overnight charge, and you can also see a few days before the change when solar managed to charge the batteries back up to 100% and then we ended up exporting to the grid. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="1600" height="722" srcset="https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 1600w" sizes="(min-width: 720px) 720px"></figure><p></p><p>Apart from some testing on the two nights where I let them charge back to 100% SoC, you can see that the automation is now keeping the batteries at a much better maximum SoC. </p><p></p><h4 id="responding-to-dynamic-pricing">Responding to dynamic pricing</h4><p>When we had the Powerwalls and the solar array installed, we didn't have a dynamic pricing tariff. Our plan was for the solar production and battery power to carry us from 05:30 all the way through to 23:30, completely avoiding the peak costs, and load shifting our entire usage into the off-peak window. That works perfectly well and we don't need to worry about responding to the dynamic pricing from a cost standpoint. </p><p>The other thing that's changed for us is that we now have two electric cars using the EV charger at home. Both of our cars are programmed to only charge during the off-peak window of 23:30 to 05:30, and this hasn't caused any problems for us so far either. We don't do enough driving that we've ever worried about both of us needing to charge at the same time, nor during peak time. The other consideration is that the cars will charge from the Powerwall batteries if they think the grid power is expensive, and ideally we want the cars to draw power from the grid, not the batteries. </p><p>All of that said, it'd still be nice to have the ability to respond to the dynamic pricing and absorb some power from the grid when it's cheap, and charge the cars on grid power rather than battery power if we ever wanted to. The question is, how do I do that? This isn't something the Powerwall is designed to do either.</p><p></p><h4 id="utilising-powerwall-settings-to-achieve-our-goal">Utilising Powerwall settings to achieve our goal</h4><p>Looking at the settings that are surfaced through Teslemetry, there isn't a huge amount of options to go at, so it was easy enough to explore them and see if any of them could be used to do what I need. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image-1.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="407" height="514"></figure><p></p><p>I'm already leveraging the 'Allow charging from grid' setting as we've seen above, and the 'Allow Export' and 'Storm Watch' aren't going to be useful here. That leaves the 'Operation Mode' and 'Backup Reserve' options to explore. </p><p></p><figure class="kg-card kg-image-card"><img src="https://scotthelme.ghost.io/content/images/2025/04/image-2.png" class="kg-image" alt="Hacking my Tesla Powerwalls to be the ultimate home energy solution!" loading="lazy" width="415" height="582"></figure><p></p><p>The Operation Mode options can be summarised as:</p><p></p><ul><li>Autonomous - Uses the time settings to load-shift as I've covered so far and does that fully automatically.</li><li>Backup - Saves 100% of the power for backups during grid outage, which isn't really useful to us. </li><li>Self Consumption - The focus is on charging from solar and using 100% of that power to run the house.</li></ul><p></p><p>This means that Autonomous mode is the correct setting for us as it achieves what we set out to achieve with the battery, leaving us the Backup Reserve option to play with, which is the one that sounds like it can do what we need. </p><p>The purpose of Backup Reserve is to set the minimum SoC that the battery will drop down to during operation, always saving the amount specified for grid outages to keep you online. As you can see from my screenshots, we've always had this set to 0% as we want to use the full capacity of the batteries to load shift, and we aren't too concerned about having reserves for if the grid goes down. </p><p>What I found during testing was that if you use the Backup Reserve and Grid Charging options together, you can create some specific behaviour that results in exactly what we need. Here's a table that gives an overview of the scenarios I'm looking at, with the main one highlighted in bold:</p><p></p><table> <thead> <tr> <th>Battery SoC %</th> <th>Backup Reserve SoC %</th> <th>Grid Charging</th> <th>Battery Charging</th> <th>Power Draw</th> </tr> </thead> <tbody> <tr> <td>50%</td> <td>0%</td> <td>Disabled</td> <td>No</td> <td>From Battery</td> </tr> <tr> <td>50%</td> <td>75%</td> <td>Disabled</td> <td>No</td> <td>From Grid</td> </tr> <tr> <td>50%</td> <td>0%</td> <td>Enabled</td> <td>No</td> <td>From Battery</td> </tr> <tr> <td><strong>50%</strong></td> <td><strong>75%</strong></td> <td><strong>Enabled</strong></td> <td><strong>Yes</strong></td> <td><strong>From Grid</strong></td> </tr> <tr> <td>80%</td> <td>0%</td> <td>Disabled</td> <td>No</td> <td>From Battery</td> </tr> <tr> <td>80%</td> <td>75%</td> <td>Disabled</td> <td>No</td> <td>From Battery</td> </tr> <tr> <td>80%</td> <td>0%</td> <td>Enabled</td> <td>No</td> <td>From Battery</td> </tr> <tr> <td>80%</td> <td>75%</td> <td>Enabled</td> <td>No</td> <td>From Battery</td> </tr> </tbody> </table> <p></p><p>By controlling the Backup Reserve %, we can control whether the battery is discharging or not. If the Backup Reserve % is lower than the SoC %, the battery will discharge, and if the Backup Reserve % is higher than the SoC %, the battery will not discharge.</p><p>Alongside that, we can also control if the battery is actively charging from the grid, rather than just allowing our house to draw its power from the grid, using the Grid Charging control. We can now use these combinations of settings to get the Powerwalls to do exactly what we want! In summary:</p><p>The Powerwalls will always charge to 75% when electricity is cheap, meaning we always have spare capacity to absorb solar production, and we can use the car charger any time we like.</p><p></p><p>Here are the YAML configurations for the automations, of which there are now 4, and I've converted them to use variables for the cost and SoC thresholds so they're not hardcoded in the automations:</p><p></p><pre><code class="language-yaml">alias: Powerwall - Disable Grid Charging description: "" triggers: - trigger: time_pattern minutes: /30 conditions: - condition: state entity_id: switch.shireburn_allow_charging_from_grid state: "on" - condition: or conditions: - condition: numeric_state entity_id: >- sensor.octopus_energy_electricity_1_snip_3_current_rate above: input_number.electricity_cost_threshold - condition: numeric_state entity_id: sensor.shireburn_charge above: input_number.powerwall_soc_threshold actions: - action: switch.turn_off metadata: {} data: {} target: entity_id: switch.shireburn_allow_charging_from_grid mode: single </code></pre><p></p><pre><code class="language-yaml">alias: Powerwall - Enable Grid Charging description: "" triggers: - trigger: time_pattern minutes: /30 conditions: - condition: state entity_id: switch.shireburn_allow_charging_from_grid state: "off" - condition: numeric_state entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate below: input_number.electricity_cost_threshold enabled: true - condition: numeric_state entity_id: sensor.shireburn_charge below: input_number.powerwall_soc_threshold actions: - action: switch.turn_on metadata: {} data: {} target: entity_id: switch.shireburn_allow_charging_from_grid mode: single </code></pre><p></p><pre><code class="language-yaml">alias: Powerwall - Set Backup Reserve To 0% description: "" triggers: - trigger: time_pattern minutes: /30 conditions: - condition: numeric_state entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate above: input_number.electricity_cost_threshold - condition: template value_template: "{{ states('number.shireburn_backup_reserve') | int != 0 }}" actions: - action: number.set_value metadata: {} data: value: "0" target: entity_id: number.shireburn_backup_reserve mode: single </code></pre><p></p><pre><code class="language-yaml">alias: Powerwall - Set Backup Reserve To 75% description: "" triggers: - trigger: time_pattern minutes: /30 conditions: - condition: numeric_state entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate below: input_number.electricity_cost_threshold - condition: template value_template: >- {{states('number.shireburn_backup_reserve') | int != states('input_number.powerwall_soc_threshold') | int}} actions: - action: number.set_value metadata: {} data: value: "75" target: entity_id: number.shireburn_backup_reserve mode: single </code></pre><p></p><p>We've had these running for a few days now and, as far as I can see, they seem to be behaving exactly how I want them to behave! That said, if you're a Powerwall expert, Home Assistant expert, or have just spotted something that I can do be doing better, drop by the comments below and let me know!</p><p></p><p><strong><em>Update 13th June 2025</em></strong>: There is now a follow up to this post with further improvements!</p><p><a href="https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io" rel="noreferrer">V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!</a></p><p></p><p></p> <!--kg-card-begin: html--> <style> pre[class*="language-"], code[class*="language-"] { font-size: 0.85em !important; } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==" crossorigin="anonymous" referrerpolicy="no-referrer"> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js" integrity="sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js" integrity="sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <!--kg-card-end: html--> ]]></content:encoded></item></channel></rss>
{
"accept-ranges": "bytes",
"age": "10733",
"alt-svc": "clear",
"cache-control": "public, max-age=0",
"cf-cache-status": "DYNAMIC",
"cf-ray": "9c508827f0cfcf43-CMH",
"connection": "keep-alive",
"content-length": "228782",
"content-type": "application/rss+xml; charset=utf-8",
"date": "Wed, 28 Jan 2026 12:35:49 GMT",
"etag": "W/\"37dae-pzjSKbVy1+5heVdlYID/p4niF+I\"",
"ghost-fastly": "true;production",
"server": "cloudflare",
"status": "200 OK",
"vary": "Cookie",
"via": "1.1 varnish, 1.1 varnish, 1.1 varnish",
"x-cache": "MISS, HIT, HIT",
"x-cache-hits": "0, 47, 0",
"x-request-id": "96da3053-ae21-4fe1-8e0e-34f7c3d948db",
"x-served-by": "cache-ams21051-AMS, cache-ams2100100-AMS, cache-cmh1290037-CMH",
"x-timer": "S1769603749.189891,VS0,VE2"
}
{
"meta": {
"type": "rss",
"version": "2.0"
},
"language": null,
"title": "Scott Helme",
"description": "Hi, I'm Scott Helme, a Security Researcher, Entrepreneur and International Speaker. I'm the creator of Report URI and Security Headers, and I deliver world renowned training on Hacking and Encryption.",
"copyright": null,
"url": "https://scotthelme.ghost.io/",
"self": "https://scotthelme.ghost.io/rss/",
"published": null,
"updated": "2026-01-28T09:36:56.000Z",
"generator": {
"label": "Ghost 6.14",
"version": null,
"url": null
},
"image": {
"title": "Scott Helme",
"url": "https://scotthelme.ghost.io/favicon.png"
},
"authors": [],
"categories": [],
"items": [
{
"id": "69638d1da2de7d0001fa2bb3",
"title": "Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us",
"description": "<p>Dogfooding is often talked about as a best practice, but I don't often see the results of such activities. For all new features introduced on Report URI, we are always the first to try them out and see how they work. In this post, we'll look</p>",
"url": "https://scotthelme.ghost.io/eating-our-own-dogfood-what-running-report-uri-on-report-uri-taught-us/",
"published": "2026-01-28T09:36:48.000Z",
"updated": "2026-01-28T09:36:48.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\"><p>Dogfooding is often talked about as a best practice, but I don't often see the results of such activities. For all new features introduced on Report URI, we are always the first to try them out and see how they work. In this post, we'll look at a few examples of issues that we found on Report URI using Report URI, and how you can use our platform to identify exactly the same kind of problems!</p><p></p><h4 id=\"dogfooding\">Dogfooding</h4><p>If you're not familiar with the term dogfooding, or 'eating your own dogfood', here's how the Oxford English Dictionary defines it:</p><p></p><blockquote>Computing slang<br>Of a company or its employees: to use a product or service developed by the company, as a means of testing it before it is made available to customers.</blockquote><p></p><p>It's pretty straightforward and something that we've been doing quite literally since the dawn of Report URI over a decade ago. Any new feature that we introduce gets deployed on Report URI first of all, prompting changes, improvements or fixes as required. Once things are going well, we then introduce a small selection of our customers to participate in a closed beta, again to illicit feedback for the same improvement cycle. After that, the feature will go to an open beta, and finally on to general availability. We currently have two features in the final open beta stages of this process, <a href=\"https://scotthelme.co.uk/capture-javascript-integrity-metadata-using-csp/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">CSP Integrity</a> and <a href=\"https://scotthelme.co.uk/integrity-policy-monitoring-and-enforcing-the-use-of-sri/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Integrity Policy</a>, and it was during the dogfooding stage of these features that some of things we're doing to discuss were found.</p><p></p><h4 id=\"integrity-policyfinding-scripts-missing-sri-protection\">Integrity Policy - finding scripts missing SRI protection</h4><p>If you're not familiar with Subresource Integrity (SRI), you can read my blog post on <a href=\"https://scotthelme.co.uk/subresource-integrity/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Subresource Integrity: Securing CDN loaded assets</a>, but here's is a quick explainer. When loading a script tag, especially from a 3rd-party, we have no control over the script we get served, and that can lead to some pretty big problems if the script is modified in a malicious way. </p><p>SRI allows you to specify an integrity attribute on a script tag so that the browser can verify the file once it downloads it, protecting against malicious modification. Here's a simple example of a before and after for a script tag without SRI and then with SRI.</p><pre><code>//before\n<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js\">\n</script>\n\n//after\n<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js\" \nintegrity=\"sha256-ivk71nXhz9nsyFDoYoGf2sbjrR9ddh+XDkCcfZxjvcM=\" \ncrossorigin=\"anonymous\">\n</script></code></pre><p></p><p>There have been literally countless examples where SRI would have saved organisations from costly attacks and data breaches, including that time when <a href=\"https://scotthelme.co.uk/protect-site-from-cryptojacking-csp-sri/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">governments all around the World got hacked</a> because they didn't use it. That said, it can be difficult to enforce the use of SRI and make sure all of your script tags have it, until <a href=\"https://scotthelme.co.uk/integrity-policy-monitoring-and-enforcing-the-use-of-sri/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Integrity Policy</a> came along. You can now trivially ensure that all scripts across your application are loaded using SRI by adding a simple HTTP Response Header.</p><pre><code>Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(default)</code></pre><p></p><p>This will ask the browser to send a report for any script that is loaded without using SRI, and then, of course, we can go and fix that!</p><p>Here's a couple that we'd missed. First, we had this one come through.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-16.png\" class=\"kg-image\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\" loading=\"lazy\" width=\"1742\" height=\"100\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-16.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-16.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2026/01/image-16.png 1600w, https://scotthelme.ghost.io/content/images/2026/01/image-16.png 1742w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>This is interesting because it really should have SRI as something in the account section, and it turns out it almost did, but there was a typo!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-15.png\" class=\"kg-image\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\" loading=\"lazy\" width=\"1449\" height=\"243\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-15.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-15.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-15.png 1449w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>That's an easy fix, and we found another script without SRI a little later too.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-13.png\" class=\"kg-image\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\" loading=\"lazy\" width=\"1225\" height=\"375\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-13.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-13.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-13.png 1225w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>This script was in our staff admin section and it was missing SRI, it was reported, and we fixed it!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-14.png\" class=\"kg-image\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\" loading=\"lazy\" width=\"1447\" height=\"147\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-14.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-14.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-14.png 1447w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Another great thing to consider about this is that you will only receive telemetry reports when there's a problem. If everything is running as it should be on your site, then no telemetry will be sent!</p><p></p><h4 id=\"content-security-policyfinding-assets-that-shouldnt-be-there\">Content Security Policy - finding assets that shouldn't be there</h4><p>The whole point of CSP is to get visibility into what's happening on your site, and that can be what assets are loading, where data is being communicated, and much more. Often, we're looking for indicators of malicious activity, like JavaScript that shouldn't be present, or data being somewhere it shouldn't be. But, sometimes, we can also detect mistakes that were made during development!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-18.png\" class=\"kg-image\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\" loading=\"lazy\" width=\"1495\" height=\"163\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-18.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-18.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-18.png 1495w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>We started getting reports for these images loading on our site and because they're not from an approved source, they were blocked and reported to us. Looking closely, the images are from our <code>.io</code> domain instead of our <code>.com</code> domain, which is what we use in test/dev environments, but not in production. It seems that someone had inadvertently hardcoded a hostname that they should not have hardcoded and our CSP let us know!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-17.png\" class=\"kg-image\" alt=\"Eating Our Own Dogfood: What Running Report URI on Report URI Taught Us\" loading=\"lazy\" width=\"1452\" height=\"103\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-17.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-17.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-17.png 1452w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Another simple fix for an issue detected quickly and easily using CSP.</p><p></p><h4 id=\"but-normally-we-dont-find-anything\">But normally we don't find anything!</h4><p>Of course, you're only ever going to find a problem by deploying our product if you had a problem to find in the first place. Our goal is always to test these features out and make sure they're ready for our customers, but sometimes, we do happen to find issues in our own site.</p><p>I guess that's really part of the value proposition though, the difference between <em>thinking</em> you don't have a problem and <em>knowing</em> you don't have a problem. Whether or not we'd found anything by deploying these features, we'd have still massively improved our awareness because we could then be confident we didn't have those issues. </p><p>It just so happens that we didn't think we had any problems, but it turns out we did! Do you think you don't have any problems on your site?</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp",
"image": "https://scotthelme.ghost.io/content/images/2026/01/dogfooding.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "Content Security Policy",
"term": "Content Security Policy",
"url": null
},
{
"label": "Integrity Policy",
"term": "Integrity Policy",
"url": null
}
]
},
{
"id": "694688d8ab4aac00016ee79e",
"title": "Blink and you'll miss them: 6-day certificates are here!",
"description": "<p>What a great way to start 2026! Let's Encrypt have now made their short-lived certificates <a href=\"https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability?ref=scotthelme.ghost.io\" rel=\"noreferrer\">available</a>, so you can go and start using them right away.</p><p>It wasn't long ago when the <a href=\"https://scotthelme.co.uk/shorter-certificates-are-coming/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">announcement</a> came that by 2029, all certificates will be reduced to a maximum of</p>",
"url": "https://scotthelme.ghost.io/blink-and-youll-miss-them-6-day-certificates-are-here/",
"published": "2026-01-19T10:48:24.000Z",
"updated": "2026-01-19T10:48:24.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp\" alt=\"Blink and you'll miss them: 6-day certificates are here!\"><p>What a great way to start 2026! Let's Encrypt have now made their short-lived certificates <a href=\"https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability?ref=scotthelme.ghost.io\" rel=\"noreferrer\">available</a>, so you can go and start using them right away.</p><p>It wasn't long ago when the <a href=\"https://scotthelme.co.uk/shorter-certificates-are-coming/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">announcement</a> came that by 2029, all certificates will be reduced to a maximum of 47 days validity, and here we are already talking about certificates valid for less than 7 days. Let's Encrypt continue to drive the industry forwards and considerably exceed the reasonable expectations of today.</p><p></p><h4 id=\"getting-a-short-lived-certificate\">Getting a short-lived certificate</h4><p>Of course, what you want to know is how to get one of these certificates! How you do this will change slightly depending on which tool you're using, but you need to specify the <code>shortlived</code> certificate profile when requesting your certificate from Let's Encrypt.</p><p>I'm using <a href=\"https://acme.sh/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">acme.sh</a> and here is the command I used to get one of these certs when I started playing with this last year:</p><pre><code>acme.sh --issue --dns dns_cf -d six-days.scotthelme.co.uk --force --keylength ec-256 --server letsencrypt --cert-profile shortlived</code></pre><p></p><p>After the certificate was issued, I got my notification from Certificate Transparency monitoring via <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a>, and I could see the full details of the certificate. Here they are:</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image.png\" class=\"kg-image\" alt=\"Blink and you'll miss them: 6-day certificates are here!\" loading=\"lazy\" width=\"870\" height=\"555\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image.png 870w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Just look at that validity period!!</p><p><strong>Valid From</strong>: 15 Nov 2025<br><strong>Valid To</strong>: 22 Nov 2025</p><p>You can find the full details on the <code>shortlived</code> certificate profile from Let's Encrypt, and other supported profiles, on <a href=\"https://letsencrypt.org/docs/profiles/?ref=scotthelme.ghost.io#shortlived\" rel=\"noreferrer\">this page</a>. </p><p></p><h4 id=\"its-not-just-lets-encrypt\">It's not just Let's Encrypt</h4><p>The good news is that Let's Encrypt isn't the only place that you can get your 6-day certificates from either! <a href=\"https://scotthelme.co.uk/another-free-ca-to-use-via-acme/?ref=scotthelme.co.uk\" rel=\"noreferrer\">Google Trust Services</a> also allows you to obtain short-lived certificates, and they have a little more flexibility in that you can request a specific number of days too. Maybe you want 6 days, 12 days, 33 days... Just specify your desired validity period in the request with your ACME client:</p><pre><code>acme.sh --issue --dns dns_cf -d six-days.scotthelme.co.uk --keylength ec-256 --server https://dv.acme-v02.api.pki.goog/directory --extended-key-usage serverAuth --valid-to '+6d'</code></pre><p></p><p>That's another source of 6-day certificates for you, but it did get me wondering.</p><p></p><h4 id=\"how-low-can-you-go\">How low can you go?..</h4><p>Well, fellow certificate nerds, you read my mind!</p><p>The Let's Encrypt <code>shortlived</code> profile doesn't allow for configurable validity periods, none of their profiles do, but GTS does allow for configuration of the validity period... 😎</p><pre><code>acme.sh --issue --dns dns_cf -d one.scotthelme.co.uk --keylength ec-256 --server https://dv.acme-v02.api.pki.goog/directory --extended-key-usage serverAuth --valid-to '+1d'</code></pre><p></p><p>Yes, that command does work, and yes you do get a <strong>ONE-DAY CERTIFICATE</strong>!!</p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-1.png\" class=\"kg-image\" alt=\"Blink and you'll miss them: 6-day certificates are here!\" loading=\"lazy\" width=\"936\" height=\"608\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-1.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-1.png 936w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Just to prove that this really is a thing, here's the PEM encoded certificate!</p><pre><code>-----BEGIN CERTIFICATE-----\nMIIDdTCCAl2gAwIBAgIQAzk1h8YknV4TAJJazYXsrjANBgkqhkiG9w0BAQsFADA7\nMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww\nCgYDVQQDEwNXUjEwHhcNMjUxMjE3MjEzNjE1WhcNMjUxMjE4MjIzNjExWjAfMR0w\nGwYDVQQDExRvbmUuc2NvdHRoZWxtZS5jby51azBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABN77LaCQqPHQ1Qx4CsEyiEVARRV5WP+qH9ZyLfO9GzJ+tLfDxROHvYPL\nYNaCgEiGBbkTOOPOX9qXJPz/g/2AQRejggFaMIIBVjAOBgNVHQ8BAf8EBAMCB4Aw\nEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUPnLq\nWpoXyBO+jzNGlH1qUr6SYHkwHwYDVR0jBBgwFoAUZmlJ1N4qnJEDz4kOJLgOMANu\niC4wXgYIKwYBBQUHAQEEUjBQMCcGCCsGAQUFBzABhhtodHRwOi8vby5wa2kuZ29v\nZy9zL3dyMS9BemswJQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMS5j\ncnQwHwYDVR0RBBgwFoIUb25lLnNjb3R0aGVsbWUuY28udWswEwYDVR0gBAwwCjAI\nBgZngQwBAgEwNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3Ix\nL0FwMDR2SjA1Q3lBLmNybDATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0B\nAQsFAAOCAQEAMsMT7AsLtQqzm0FSsDBq33M9/FAz+Su86NQurk8MXXrSjUrdSKhh\nzTv2whJcC0W3aPhoqMeeqpsLYQ4AiLgBS2LPoJz2HuFsIfOddrpI3lOHXssT2Wpc\nMjofbwEOfkDk+jV/rqbz1q+cjbM2VGfoxILgcA7KxVZX0ylvZf52c2zpA9v+sXKu\npPFKHHDX2UNSfsPODmWLVWdfFk/ZFbr09urei8ZgdsJhRKABD9BW3aV8QP2dMISh\nOH6fWcJXrp/w1NjJqIKidMiMgaCe5TDb+j5gOJ+ZLVcKA4WdLtcVYpQIXiT8mIeO\nrCYNVSkFN4ZIONedfENemM5GgBqcqbpxMA==\n-----END CERTIFICATE-----</code></pre><p></p><h4 id=\"automation-is-king\">Automation is King</h4><p>The great thing about this, and I've been using these certs for weeks now, is that once you're using an ACME client, you're already automated, and once you're automated, the validity period really isn't relevant any more. I'm currently sticking with the 6-day certs, and I will alternate between Let's Encrypt and Google Trust Services, but running these automations more frequently to go from 90 days down to 6 days really doesn't change anything at all, so give it a try!</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp",
"image": "https://scotthelme.ghost.io/content/images/2026/01/6-day-certs.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Let's Encrypt",
"term": "Let's Encrypt",
"url": null
},
{
"label": "Google Trust Services",
"term": "Google Trust Services",
"url": null
},
{
"label": "TLS",
"term": "TLS",
"url": null
},
{
"label": "Short-Lived Certificates",
"term": "Short-Lived Certificates",
"url": null
}
]
},
{
"id": "6962376aa2de7d0001fa2a36",
"title": "What a Year of Solar and Batteries Really Saved Us in 2025",
"description": "<p>Throughout 2025, I spoke a few times about our home energy solution, including our grid usage, our solar array and our Tesla Powerwall batteries. Now that I have a full year of data, I wanted to take a look at exactly how everything is working out, and, in alignment with</p>",
"url": "https://scotthelme.ghost.io/what-a-year-of-solar-and-batteries-really-saved-us-in-2025/",
"published": "2026-01-13T11:24:31.000Z",
"updated": "2026-01-13T11:24:31.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\"><p>Throughout 2025, I spoke a few times about our home energy solution, including our grid usage, our solar array and our Tesla Powerwall batteries. Now that I have a full year of data, I wanted to take a look at exactly how everything is working out, and, in alignment with our objectives, how much money we've saved!</p><p></p><h4 id=\"our-setup\">Our setup</h4><p>Just to give a quick overview of what we're working with, here are the details on our solar, battery and tariff situation:</p><ul><li>☀️Solar Panels: We have 14x Perlight solar panels managed by Enphase that make up the 4.2kWp array on our roof, and they produce energy when the sun shines, which isn't as often as I'd like in the UK!</li><li>🔋Tesla Powerwalls: We have 3x Tesla Powerwall 2 in our garage that were purchased to help us load-shift our energy usage. Electricity is very expensive in the UK and moving from peak usage which is 05:30 to 23:30 at ~£0.28/kWh, to off-peak usage, which is 23:30 - 05:30 at ~£0.07/kWh, is a significant cost saving.</li><li>💡Smart Tariff: My wife and I both drive electric cars and our electricity provider, Octopus Energy, has a Smart Charging tariff. If we plug in one of our cars, and cheap electricity is available, they will activate the charger and allow us to use the off-peak rate, even at peak times.</li></ul><p></p><p>Now that we have some basic info, let's get into the details!</p><p></p><h4 id=\"grid-import\">Grid Import</h4><p>I have 3 sources of data for our grid import, and all of them align pretty well in terms of their measurements. I have the amount our electricity supplier charged us for, I have my own CT Clamp going via a Shelly EM that feeds in to Home Assistant, and I have the Tesla Gateway which controls all grid import into our home.</p><p>Starting with my Home Assistant data, these are the relevant readings. </p><p>Jan 1st 2025 - 15,106.10 kWh<br>Dec 31st 2025 - 36,680.90 kWh<br>Total: 21,574.80 kWh<br><strong>Total Import: 21.6 MWh</strong></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-3.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"1138\" height=\"503\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-3.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-3.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-3.png 1138w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>As you can see in the graph, during the summer months we have slightly lower grid usage and the graph line climbs at a lower rate, but overall, we have pretty consistent usage. Looking at what our energy supplier charged, us for, that comes in slightly lower.</p><p><strong>Total Import: 20.1 MWh</strong></p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-8.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"997\" height=\"577\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-8.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-8.png 997w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>I'm going to use the figure provided by our energy supplier in my calculations because their equipment is likely more accurate than mine, and also, what they're charging me is the ultimate thing that matters. The final source is our Tesla Gateway, which shows us having imported 21.0 MWh.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-11.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"1290\" height=\"1568\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-11.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-11.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-11.png 1290w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>It's great to see how all of these sources of data align so poorly! 😅</p><h4 id=\"grid-export\">Grid Export</h4><p>Looking at our export, the graph tells a slightly different story because, as you can see, we didn't really start exporting properly until June, when our export tariff was activated. Prior to June, it simply wasn't worth exporting as we were only getting £0.04/kWh but at the end of May, our export tariff went live and we were then getting paid £0.15/kWh for export. My <a href=\"https://scotthelme.co.uk/automation-improvements-after-a-tesla-powerwall-outage/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">first</a> and <a href=\"https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">second</a> blog posts cover the full details of this change when it happened if you'd like to read them but for now, just note that it will change the calculations a little later as we only had export for 60% of the year.</p><p><strong>Total Export: 6.0 MWh</strong></p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-9.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"989\" height=\"582\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-9.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-9.png 989w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>With our grid export covered the final piece of the puzzle is to look at our solar.</p><p></p><h4 id=\"solar-production\">Solar Production</h4><p>We're really not in the best part of the world for generating solar power, but we've still managed to produce quite a bit of power. Even in the most ideal, perfect scenario, our solar array can only generate 4.2kW of power, and we're definitely never getting near that. Our peak production was 2.841kW on 8th July at 13:00, and you can see our full annual production graph here. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-12.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"1582\" height=\"513\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-12.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-12.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-12.png 1582w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Looking at the total energy production for the entire array, you can see it pick up through the sunnier months but remain quite flat during the darker days of the year.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-2.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"1132\" height=\"504\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-2.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-2.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-2.png 1132w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Jan 1st 2025 - 2.709 MWh<br>Dec 31st 2025 - 5.874 MWh<br><strong>Solar Production: 3.2 MWh</strong></p><p></p><p>Just to confirm, I also took a look at the Enphase app, which is drawing it's data from the same source to be fair, and it agrees with the 3.2 MWh of generation.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-4.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"1157\" height=\"560\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2026/01/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2026/01/image-4.png 1157w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><h4 id=\"calculating-the-savings\">Calculating the savings</h4><p>This isn't exactly straightforward because of the combination of our solar array and excess import/export due to the batteries, but here are the numbers I'm currently working on.</p><p><strong>Total Import: 20.1 MWh<br>Total Export: 6.0 MWh<br>Solar Production: 3.2 MWh</strong></p><p></p><p>That gives us a total household usage of 17.3 MWh.</p><p>(20.1 MWh import + 3.2 MWh solar) − 6.0 MWh export = 17.3 MWh usage</p><p>If we didn't have the solar array providing power, the full 17.3 MWh of consumption would have been chargeable from our provider. If we had only the solar and no battery, assuming a perfect ability to utilise our solar generation, only 14.1 MWh of our usage would need to be imported. The cost of those units of solar generation can be viewed at the peak and off-peak rates as follows.</p><p>Peak rate: 3,200 kWh x £0.28/kWh = £896<br>Off-peak rate: 3,200 kWh x £0.07/kWh = £224</p><p>Given that solar panels only produce during peak electricity rates, it would be reasonable to use the higher price here. A consideration for us though is that we do have batteries, and we're able to load-shift all of our usage into the off-peak rate, so arguably the solar panels only made £224 of electricity. </p><p>The bigger savings come when we start to look at the cost of the grid import. Assuming we had no solar panels, we'd have imported 17.3 MWh of electricity, and with the solar panels and perfect utilisation, we'd have imported 14.1 MWh of electricity. That's quite a lot of electricity and calculating the different costs of peak vs. off-peak by using batteries to load shift our usage gives some quite impressive results.</p><p>Peak rate: 17,300 kWh x £0.28/kWh = £4,844<br>Peak rate with solar: 14,100 kWh x £0.28 = £3,948</p><p>Off-peak rate: 17,300 kWh x £0.07/kWh = £1,211<br>Off-peak rate with solar: 14,100 kWh x £0.07/kWh = £987</p><p>This means there's a potential swing from £4,844 down to £987 with solar and battery, a total potential saving of £3,857!</p><p>This also tracks if we look at our monthly spend on electricity which went from £350-£400 per month down to £50-£100 per month depending on the time of year. But it gets better.</p><p></p><h4 id=\"exporting-excess-energy\">Exporting excess energy</h4><p>Our solar array generates almost nothing in the winter months so our batteries are sized to allow for a full day of usage with basically no solar support. We can go from the start of the peak rate at 05:30 all the way to the off-peak rate at 23:30 without using any grid power. When it comes to the summer months, though, our solar array is producing a lot of power and we clearly have a capability to export a lot more. The batteries can fill up on the off-peak rate overnight at £0.07/kWh, and then export it during the peak rate for £0.15/kWh, meaning any excess solar production or battery capacity can be exported for a reasonable amount.</p><p>If we take a look at the billing information from our energy supplier, we can see that during July, our best month for solar production, we exported a lot of energy. We exported so much energy that it actually fully offset our electricity costs and allowed us to go negative, meaning we were earning money back.</p><p>Here is our electricity import data:</p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-7.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"988\" height=\"623\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-7.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-7.png 988w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>And here is our electricity export data:</p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2026/01/image-6.png\" class=\"kg-image\" alt=\"What a Year of Solar and Batteries Really Saved Us in 2025\" loading=\"lazy\" width=\"983\" height=\"706\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2026/01/image-6.png 600w, https://scotthelme.ghost.io/content/images/2026/01/image-6.png 983w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>That's a pretty epic scenario, despite us being such high energy consumers, to still have the ability to fully cover our costs and even earn something back! For clarity, we will still have the standing charge component of our bill, which is £0.45/day so about £13.50 per month to go on any given month, but looking at the raw energy costs, it's impressive.</p><p></p><h4 id=\"the-final-calculation\">The final calculation</h4><p>I pulled all of our charges for electricity in 2025 to see just how close my calculations were and to double check everything I was thinking. Earlier, I gave these figures:</p><p>Off-peak rate: 17,300 kWh x £0.07/kWh = £1,211</p><p>If 100% of our electricity usage was at the off-peak rate, we should have paid £1,211 for the year. Adding up all of our monthly charges, our total for the year was £1,608.11 all in, but we need to subtract our standing charge from that.</p><p>Total cost = £1,608.11 - (365 * £0.45)<br><strong>Total import = £1,443.86</strong></p><p></p><p>This means that we got almost all of our usage at the off-peak rate which is an awesome achievement! After the charges for electricity, I then tallied up all of our payments for export.</p><p><strong>Total export = £886.49</strong></p><p></p><p>Another pretty impressive achievement, earning so much in export, which also helps to bring our net electricity cost in 2025 to <strong>£557.37</strong>! To put this another way, the effective rate of our electricity is now just £0.03/kWh.</p><p>£557.37 / 17,300kWh = <strong>£0.03/kWh</strong></p><p></p><h4 id=\"but-was-it-all-worth-it\">But was it all worth it?</h4><p>That's a tricky question to answer, and everyone will have different objectives and desired outcomes, but ours was pretty clear. Running two Electric Vehicles, having two adults working from home full time, me having servers and equipment at home, along with a power hungry hot tub, we were spending too much per month in electricity alone, and our goal was to reduce that.</p><p>Of course, it only makes sense to spend money reducing our costs if we reduce them enough to pay back the investment in the long term, and things are looking good so far. Here are the costs for our installations:</p><p></p><p>£17,580 - Powerwalls #1 and #2 installed.<br>£13,940 - Solar array installed.<br>£7,840 - Powerwall #3 installed.<br>Total cost = £39,360</p><p></p><p>If we assume even a generous 2/3 - 1/3 split between peak and off-peak usage, with no Powerwalls or solar array, our electricity costs for 2025 would have been £3,632.86:</p><p>11,533 kWh x £0.28/kWh = £3,229.24<br>5,766 kWh x £0.07/kWh = £403.62<br>Total = £3,632.86</p><p></p><p>Instead, our costs were only £557.37, meaning we saved £3,078.49 this year. We also only had export capabilities for 7 months of 2025, so in 2026 when we will have 12 months of export capabilities, we should further reduce our costs. I anticipate that in 2026 our electricity costs for the year will be ~£0, and that's our goal.</p><p>Having our full costs returned in ~11 years is definitely something we're happy with, and we've also had protection against several power outages in our area along the way, which is a very nice bonus. Another way to look at this is that the investment is returning ~9%/year.</p><p></p><table>\n<thead>\n<tr>\n<th style=\"text-align:right\">Year</th>\n<th style=\"text-align:right\">Cumulative savings (£)</th>\n<th style=\"text-align:right\">ROI (%)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:right\">1</td>\n<td style=\"text-align:right\">3,632.86</td>\n<td style=\"text-align:right\">9.23%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">2</td>\n<td style=\"text-align:right\">7,265.72</td>\n<td style=\"text-align:right\">18.46%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">3</td>\n<td style=\"text-align:right\">10,898.58</td>\n<td style=\"text-align:right\">27.69%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">4</td>\n<td style=\"text-align:right\">14,531.44</td>\n<td style=\"text-align:right\">36.92%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">5</td>\n<td style=\"text-align:right\">18,164.30</td>\n<td style=\"text-align:right\">46.15%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">6</td>\n<td style=\"text-align:right\">21,797.16</td>\n<td style=\"text-align:right\">55.38%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">7</td>\n<td style=\"text-align:right\">25,430.02</td>\n<td style=\"text-align:right\">64.61%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">8</td>\n<td style=\"text-align:right\">29,062.88</td>\n<td style=\"text-align:right\">73.84%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">9</td>\n<td style=\"text-align:right\">32,695.74</td>\n<td style=\"text-align:right\">83.07%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">10</td>\n<td style=\"text-align:right\">36,328.60</td>\n<td style=\"text-align:right\">92.30%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">15</td>\n<td style=\"text-align:right\">54,492.90</td>\n<td style=\"text-align:right\">138.43%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">20</td>\n<td style=\"text-align:right\">72,657.20</td>\n<td style=\"text-align:right\">184.61%</td>\n</tr>\n<tr>\n<td style=\"text-align:right\">25</td>\n<td style=\"text-align:right\">90,821.50</td>\n<td style=\"text-align:right\">230.76%</td>\n</tr>\n</tbody>\n</table>\n<p> </p><p>Of course, at some point during that period, the effective value of the installation will reduce to almost £0, and we have to consider that, but it's doing pretty darn good. If we hadn't needed to add that third Powerwall, this would have been so much better too. We'll see what the future holds, but with the inevitable and continued rise of energy costs, and talk of moving the standing charge on to our unit rate, things might look even better in the future.</p><p></p><h4 id=\"onwards-to-2026\">Onwards to 2026!</h4><p>Now that we have everything properly set up, and I'm happy with all of our Home Assistant automations, we're going to see how 2026 goes. I will definitely circle back in a year from now and see how the numbers played out, and until then, I hope the information here has been useful or interesting 👍</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp",
"image": "https://scotthelme.ghost.io/content/images/2026/01/2025-energy.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Tesla Powerwall",
"term": "Tesla Powerwall",
"url": null
},
{
"label": "Solar Power",
"term": "Solar Power",
"url": null
},
{
"label": "Octopus Energy",
"term": "Octopus Energy",
"url": null
}
]
},
{
"id": "693822108d605500017a6622",
"title": "Report URI Penetration Test 2025",
"description": "<p>Every year, just as we start to put up the Christmas Tree, we have another tradition at <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a> which is to conduct our annual penetration test! </p><p>🎅🎄🎁 --> 🩻🔐🥷</p><p>This will be our 6th annual penetration test that we've posted completely publicly,</p>",
"url": "https://scotthelme.ghost.io/report-uri-penetration-test-2025/",
"published": "2025-12-15T15:36:37.000Z",
"updated": "2025-12-15T15:36:37.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp\" alt=\"Report URI Penetration Test 2025\"><p>Every year, just as we start to put up the Christmas Tree, we have another tradition at <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a> which is to conduct our annual penetration test! </p><p>🎅🎄🎁 --> 🩻🔐🥷</p><p>This will be our 6th annual penetration test that we've posted completely publicly, just as before, and we'll be covering a full run down of what was found and what we've done about it. </p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/12/image.png\" class=\"kg-image\" alt=\"Report URI Penetration Test 2025\" loading=\"lazy\" width=\"1915\" height=\"356\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/12/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/12/image.png 1600w, https://scotthelme.ghost.io/content/images/2025/12/image.png 1915w\" sizes=\"(min-width: 720px) 720px\"></a></figure><h4 id=\"penetration-tests\">Penetration Tests</h4><p>If you find this post interesting or would like to see our previous reports, then here are the links to each and every one of those!</p><p><a href=\"https://scotthelme.co.uk/report-uri-penetration-test-2020/?ref=scotthelme.co.uk\" rel=\"noreferrer\">Report URI Penetration Test 2020</a></p><p><a href=\"https://scotthelme.co.uk/report-uri-penetration-test-2021/?ref=scotthelme.co.uk\" rel=\"noreferrer\">Report URI Penetration Test 2021</a></p><p><a href=\"https://scotthelme.co.uk/report-uri-penetration-test-2022/?ref=scotthelme.co.uk\" rel=\"noreferrer\">Report URI Penetration Test 2022</a></p><p><a href=\"https://scotthelme.co.uk/report-uri-penetration-test-2023/?ref=scotthelme.co.uk\" rel=\"noreferrer\">Report URI Penetration Test 2023</a></p><p><a href=\"https://scotthelme.co.uk/report-uri-penetration-test-2024/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI Penetration Test 2024</a></p><p></p><h4 id=\"the-results\">The Results</h4><p>2025 has been another good year for us as we've continued to focus on the security of our product, and the results of the test show that. Not only have we added a bunch of new features, we've also made some significant changes to our infrastructure and made countless changes and improvements to existing functionality too. The tester had what was effectively an unlimited scope to target the application, a full 'guidebook' on how to get up and running with our product and a demo call to ensure all required knowledge was handed over before the test. We wanted them to hit the ground running and waste no time getting stuck in.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/12/image-1.png\" class=\"kg-image\" alt=\"Report URI Penetration Test 2025\" loading=\"lazy\" width=\"724\" height=\"409\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-1.png 600w, https://scotthelme.ghost.io/content/images/2025/12/image-1.png 724w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Finding an Info rated issue, three Low rated and a Medium severity issue definitely gives us something to talk about, so let's look at that Medium severity first. </p><p></p><h4 id=\"csv-formula-injection\">CSV Formula Injection</h4><p>The entire purpose of our service is to ingest user-generated data and then display that in some way. Every single telemetry report we process comes from either a browser or a mail server, and the entire content, whilst conforming to a certain schema, is essentially free in terms of the values of fields. We have historically focused on, and thus far prevented, XSS from creeping it's way in, but this bug takes a slightly different form. </p><p>Earlier this year, June 11th to be exact, we released a new feature that allowed for a raw export of telemetry data. This was a commonly requested feature from our customers and we provided two export formats for the data, the native JSON that telemetry is ingested in, or a CSV variant too. You can see the export feature being used here on CSP Reports.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/12/image-2.png\" class=\"kg-image\" alt=\"Report URI Penetration Test 2025\" loading=\"lazy\" width=\"1553\" height=\"543\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-2.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-2.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-2.png 1553w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Because the data export is a raw export, we are providing the telemetry payloads that we received from the browser or email server, just as you would have received them if you collected them yourself. It turns out that as these fields obviously contain user generated data, and you can do some trickery!</p><p>The specific example given by the tester uses a NEL report, but it's not a problem specific to NEL reports, it's possible across all of our telemetry. Looking at the example in the report, you can see how the tester crafted a specific payload:</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/12/image-3.png\" class=\"kg-image\" alt=\"Report URI Penetration Test 2025\" loading=\"lazy\" width=\"1005\" height=\"671\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-3.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-3.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-3.png 1005w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>This <code>type</code> value contains an Excel command that will make it through to the CSV export as it represents the raw value sent by the client. The steps to leverage this are pretty convoluted, but I will quickly summarise them here by using an example if you want to target my good friend <a href=\"https://troyhunt.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Troy Hunt</a>, who runs <a href=\"https://haveibeenpwned.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Have I Been Pwned</a> which indeed uses Report URI.</p><ol><li>Identify the subdomain that Troy uses on our service, which is public information. </li><li>Send telemetry events to that endpoint with specifically crafted payloads which will be processed in to Troy's account.</li><li>Troy would then need to view that data in our UI, and for any reason wish to export that data as a CSV file. </li><li>Then, Troy would have to open that CSV file specifically in Excel, and bypass the two security warnings presented when opening the file. </li></ol><p></p><p>There are a couple of points in there that require some very specific actions from Troy, and the chances of all of those things happening are a little far-fetched, but still, we gave it a lot of consideration. The problem I had is that the export is raw, it's meant to be an export of what the browser or email server provided to us so you have a verbatim copy of the raw data. Sanitising that data is also tricky as there isn't a universal way to sanitise CSV because it depends on what application you're going to open it with, something I discovered when testing out our fix!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/12/image-4.png\" class=\"kg-image\" alt=\"Report URI Penetration Test 2025\" loading=\"lazy\" width=\"1140\" height=\"582\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-4.png 1140w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Our current approach is to add a single quote to the start of the value if we detect that the first non-whitespace character is a potentially dangerous character, and that seems to have reliably solved the issue. I'm happy to hear feedback on this approach, or alternative suggestions, so drop by the comments below if you can contribute!</p><p></p><h4 id=\"vulnerabilities-in-outdated-dependencies\">Vulnerabilities in Outdated Dependencies </h4><p>Not again! Actually, I'm pretty happy with the finding here, but I will explain both of the issues raised and what we did and didn't do about them. </p><h6 id=\"bootstrap-v3x\">Bootstrap v3.x</h6><p>The last version of Bootstrap 3 was v3.4.1 which did have an XSS vulnerability present (<a href=\"https://security.snyk.io/package/npm/bootstrap/3.4.1?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>). We have since cloned v3.4.1 and patched it up to v3.4.4 ourselves to fix various issues that have been found, including the XSS issue raised here. The issue is still flagged in v3.x though, which is why it was flagged in our custom patched version, so in reality, there is no problem here and we're happy to keep using our own version. </p><h6 id=\"jquery-cookie-v141\">jQuery Cookie v1.4.1</h6><p>Another tricky one because the Snyk data that we refer to lists no vulnerability in this library (<a href=\"https://security.snyk.io/package/npm/jquery.cookie/1.4.1?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>) which is why our own tooling hasn't flagged this to us. That said, the NVD does list a CVE for this version of the jQuery Cookie plugin (<a href=\"https://nvd.nist.gov/vuln/detail/cve-2022-23395?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>) but I can't find other data to back that up, including their own link to Snyk which doesn't list a vulnerability. Rather than spend too much time on this for what is a relatively simple plugin, we decided to remove it and implement the functionality that we need ourselves, solving the problem.</p><p></p><p>With both of those issues addressed, I'm happy to say that we can consider this as resolved too.</p><p></p><h4 id=\"insufficient-session-expiration\">Insufficient Session Expiration</h4><p>This issue has been raised previously in our <a href=\"https://scotthelme.co.uk/report-uri-penetration-test-2021/?ref=scotthelme.co.uk\" rel=\"noreferrer\">2021 penetration test</a>, and our position remains similar to what it was back then. Whilst I'm leaning towards 24 hours being on the top end, and we will probably bring this down shortly, I also feel like the recommended 20 minutes is just too short. Going away from your desk for a coffee break and returning to find you've been logged out just seems a little bit too aggressive for us.</p><p>Looking at the other suggested concerns, if you have malware running on your endpoint, or someone gains physical access to extract a session cookie, I feel like you probably have much bigger concerns to address too! Overall, I acknowledge the issue raised and we're currently thinking something in the 12-18 hours range might be better suited. </p><p></p><h4 id=\"insecure-tls-configuration\">Insecure TLS Configuration\n</h4><p>You could argue that I know a thing or two about TLS configuration, heck, you can even attend my <a href=\"https://www.feistyduck.com/training/practical-tls-and-pki?ref=scotthelme.ghost.io\" rel=\"noreferrer\">training course</a> on it if you like! The issue that somebody like a pen tester coming in from the outside is that they're always going to lack the specific context on why we made the configuration choices we did, and I also recognise that it's very hard to give generic advice that fits all situations. </p><p>We have priority in our cipher suite list for all of the best cipher suites as some of the first choices, and we have support for the latest protocol versions too. We're doing so well in fact that we get an A grade on the SSL Labs test (<a href=\"https://www.ssllabs.com/ssltest/analyze.html?d=helios.report-uri.com&ref=scotthelme.ghost.io\" rel=\"noreferrer\">results</a>):</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/12/image-5.png\" class=\"kg-image\" alt=\"Report URI Penetration Test 2025\" loading=\"lazy\" width=\"1284\" height=\"671\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/12/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/12/image-5.png 1000w, https://scotthelme.ghost.io/content/images/2025/12/image-5.png 1284w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>I'm happy with where our current configuration is but as always, we will keep it in constant review as time goes by!</p><p></p><h4 id=\"no-account-lockout-or-timeout-mechanism\">No Account Lockout or Timeout Mechanism\n</h4><p>This is a controversial topic and it's quite interesting to see it come up because we specifically cover this in the <a href=\"https://www.troyhunt.com/workshops/?ref=scotthelme.co.uk\" rel=\"noreferrer\">Hack Yourself First</a> workshop that I deliver alongside Troy Hunt! I absolutely recognise the goal that a mechanism like this would be trying to achieve, but I worry about the potential negative side-effects.</p><p>Using account enumeration it's often trivial to determine if someone has an account on our service, so you might be able to determine that [email protected] is indeed registered. You then may want to start guessing different passwords to try and log in to Troy's account, and there lies the problem. How many times should you be able to sit there and guess a password before something happens? An account lockout mechanism might work along the lines of saying 'after 5 unsuccessful login attempts we will lock the account and require a password reset', or perhaps say 'after 5 unsuccessful login attempts you will not be able to login again for 3 minutes'. Both of these would stop the attacker from making significant progress, but both of them also present an opportunity to be abused by an attacker too. The attacker can now sit and make repeated login attempts to Troy's account and keep it in a perpetually locked state, denying him the use of his account, a Denial-of-Service attack (DoS)! It's for this reason I'm generally not fond of these account lockout / account suspension mechanisms, they do provide an opportunity for abuse. </p><p>Instead, we rely on a different set of protections to try and limit the impact of attacks like these. </p><ol><li>We implement incredibly strict rate-liming on sensitive endpoints, including our authentication endpoints. This would slow an attacker down so the rate at which they could make guesses would be reduced. (This was disabled for the client IP addresses used by the tester)</li><li>We utilise Cloudflare's Bot Management across our application, which includes authentication flows, and if there is any reasonable suspicion that the client is a bot or in some way automated, they would be challenged. This prevents attackers from automating their attacks, ultimately slowing them down. (This was disabled for the client IP addresses used by the tester)</li><li>We have taken exceptional measures around password security. We require strong and complex passwords for our service, check for commonly used passwords against the Pwned Passwords API, use zxcvbn for strength testing, and more. This makes it highly unlikely that the password being guessed could be guessed easily, and you can read the full details on our password security measures <a href=\"https://scotthelme.co.uk/boosting-account-security-pwned-passwords-and-zxcvbn/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">here</a>.</li><li>We support, and have a very high adoption of, 2FA across our service. This means that there's a very good chance that if the attacker was able to guess a password, the next prompt would be to input the TOTP code from the authenticator app!</li></ol><p>Given the above concerns around lockout mechanisms, and the additional measures we have in place, I continue to remain happy with our current position but we will always review these things on an ongoing basis. </p><p></p><h4 id=\"thats-a-wrap\">That's a wrap!</h4><p>Given how much continued development we see in the product, and how much our infrastructure is evolving over time, I'm really pleased to see that our continued efforts to maintain the security of our product, and ultimately our customer's data, has paid off. </p><p>As we look forward to 2026 our \"Development Horizon\" project board is loaded with cool new features and updates, so be sure to keep an eye out for the exciting new things we have coming!</p><p>If you want to download a copy of our report, the latest report is always available and linked in the <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io#footer\" rel=\"noreferrer\">footer of our site</a>!</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/12/report-uri-penetration-test.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "Penetration Test",
"term": "Penetration Test",
"url": null
}
]
},
{
"id": "691dee03d9c78000011bd122",
"title": "Report URI - outage update",
"description": "<p>This is not a blog post that anybody ever wants to write, but we had some service issues yesterday and now the dust has settled, I wanted to provide an update on what happened. The good news is that the interruption was very minor in the end, and likely went</p>",
"url": "https://scotthelme.ghost.io/report-uri-outage-update/",
"published": "2025-11-19T21:24:19.000Z",
"updated": "2025-11-19T21:24:19.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp\" alt=\"Report URI - outage update\"><p>This is not a blog post that anybody ever wants to write, but we had some service issues yesterday and now the dust has settled, I wanted to provide an update on what happened. The good news is that the interruption was very minor in the end, and likely went unnoticed by most of our customers. </p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide-1.png\" class=\"kg-image\" alt=\"Report URI - outage update\" loading=\"lazy\" width=\"974\" height=\"141\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide-1.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide-1.png 974w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><h4 id=\"what-happened\">What happened?</h4><p>I'm sure that many of you are already aware of the issues that Cloudflare experienced yesterday, and their post-mortem is now available <a href=\"https://blog.cloudflare.com/18-november-2025-outage/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">on their blog</a>. It's always tough to have service issues, but as expected Cloudflare handled it well and were transparent throughout. As a customer of Cloudflare that uses many of their services, the Cloudflare outage unfortunately had an impact on our service too. Because of the unique way that our service operates, <strong><em>our subsequent service issues did not have any impact on the websites or operations of our customers</em></strong>. What we do have to recognise, though, is that we may have missed some telemetry events for a short period of time.</p><p></p><h4 id=\"our-infrastructure\">Our infrastructure</h4><p>Because all of the telemetry events sent to us have to pass through Cloudflare first, when Cloudflare were experiencing their service issues, it did prevent telemetry from reaching our servers. If we take a look at the bandwidth for one of our many telemetry ingestion servers, we can clearly see the impact.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/ingestion-server.png\" class=\"kg-image\" alt=\"Report URI - outage update\" loading=\"lazy\" width=\"991\" height=\"280\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/ingestion-server.png 600w, https://scotthelme.ghost.io/content/images/2025/11/ingestion-server.png 991w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Looking at the graph from the Cloudflare blog showing their 500 error levels, we have a near perfect alignment with us not receiving telemetry during their peak error rates.</p><p></p><figure class=\"kg-card kg-image-card kg-card-hascaption\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/image.png\" class=\"kg-image\" alt=\"Report URI - outage update\" loading=\"lazy\" width=\"1507\" height=\"733\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/11/image.png 1000w, https://scotthelme.ghost.io/content/images/2025/11/image.png 1507w\" sizes=\"(min-width: 720px) 720px\"><figcaption><span style=\"white-space: pre-wrap;\">source: Cloudflare</span></figcaption></figure><p></p><p>The good news here, as mentioned above, is that even if a browser can't reach our service and send the telemetry to us, it has no negative impact on our customer's websites, at all, as the browser will simply continue to load the page and try to send the telemetry again later. This is a truly unique scenario where we can have a near total service outage and it's unlikely that a single customer even noticed because we have no negative impact on their application.</p><p></p><h4 id=\"the-recovery\">The recovery</h4><p>Cloudflare worked quickly to bring their service troubles under control and things started to return to normal for us around 14:30 UTC. We could see our ingestion servers start to receive telemetry again, and we started to receive much more than usual. Here's that same view for the server above.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/ingestion-server-2.png\" class=\"kg-image\" alt=\"Report URI - outage update\" loading=\"lazy\" width=\"997\" height=\"285\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/ingestion-server-2.png 600w, https://scotthelme.ghost.io/content/images/2025/11/ingestion-server-2.png 997w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>If we take a look at the aggregate inbound telemetry for our whole service, we were comfortably receiving twice our usual volume of telemetry data.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/global-telemetry.png\" class=\"kg-image\" alt=\"Report URI - outage update\" loading=\"lazy\" width=\"994\" height=\"282\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/global-telemetry.png 600w, https://scotthelme.ghost.io/content/images/2025/11/global-telemetry.png 994w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>This is a good thing and shows that the browsers that had previously tried to dispatch telemetry to us and had failed were now retrying and succeeding. We did keep a close eye on the impact that this level of load was having, and we managed it well, with the load tailing off to our normal levels overnight. Whilst this recovery was really good to see, we have to acknowledge that there will inevitably be telemetry that was dropped during this time, and it's difficult to accurately gauge how much. If the telemetry event was retried successfully by the browser, or the problem also existed either before or after this outage, we will have still processed the event and taken any necessary action. </p><p></p><h4 id=\"looking-forwards\">Looking forwards</h4><p>I've always talked openly about our infrastructure at Report URI, even blogging in detail about the issues we've faced and the changes we've made as a result, much as I am doing here. We depend on several other service providers to build our service, including Cloudflare for CDN/WAF, DigitalOcean for VPS/compute and Microsoft Azure for storage, but sometimes even the big players will have their own problems, just like AWS did recently too. </p><p>Looking back on this incident now, whilst it was a difficult process for us to go through, I believe we're still making the best choices for Report URI and our customers. The likelihood of us being able to build our own service that rivals the benefits that Cloudflare provides is zero, and looking at other service providers to migrate to seems like a knee-jerk overreaction. I'm not looking for service providers that promise to never have issues, I'm looking for service providers that will respond quickly and transparently when they inevitably do have an issue, and Cloudflare have demonstrated that again. It's also this same desire for transparency and honesty that has driven me to write this blog post to inform you that it is likely we missed some of your telemetry events yesterday, and that we continue to consider how we can improve our service further going forwards.</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/11/report-uri-down.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
}
]
},
{
"id": "691b2097bb6a68000181de36",
"title": "Integrity Policy - Monitoring and Enforcing the use of SRI",
"description": "<p>This has been a long time coming so I'm excited that we now have a working standard in the browser for monitoring and enforcing the use of SRI across your website assets!</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png\" class=\"kg-image\" alt loading=\"lazy\" width=\"974\" height=\"141\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png 974w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><h4 id=\"sri-refresher\">SRI refresher</h4><p>For those that aren't familiar, or would like a quick refresher, here&</p>",
"url": "https://scotthelme.ghost.io/integrity-policy-monitoring-and-enforcing-the-use-of-sri/",
"published": "2025-11-19T15:01:02.000Z",
"updated": "2025-11-19T15:01:02.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp\" alt=\"Integrity Policy - Monitoring and Enforcing the use of SRI\"><p>This has been a long time coming so I'm excited that we now have a working standard in the browser for monitoring and enforcing the use of SRI across your website assets!</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png\" class=\"kg-image\" alt=\"Integrity Policy - Monitoring and Enforcing the use of SRI\" loading=\"lazy\" width=\"974\" height=\"141\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/11/report-uri---wide.png 600w, https://scotthelme.ghost.io/content/images/2025/11/report-uri---wide.png 974w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><h4 id=\"sri-refresher\">SRI refresher</h4><p>For those that aren't familiar, or would like a quick refresher, here's the TLDR of SRI - Subresource Integrity. When loading assets from a 3rd-party, especially JavaScript, it's a good idea to have some control over what exactly it is you're loading. A typical script tag will allow <em>any</em> script to load in your page, but SRI gives you the ability to make sure the JavaScript you're getting is the JavaScript you wanted... It's all done with the simple addition of an integrity attribute:</p><p></p><pre><code><script\nsrc=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js\" \nintegrity=\"sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=\"\ncrossorigin=\"anonymous\">\n</script></code></pre><p></p><p>That integrity attribute means the browser can now download that file and check the cryptographic fingerprint of the file it downloaded matches the one that you were expecting! If the file is tampered with or modified in some way, the browser can reject it. I have a full blog post about SRI, <a href=\"https://scotthelme.co.uk/subresource-integrity/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Subresource Integrity: Securing CDN loaded assets</a>, that I wrote <strong><em>more than a decade ago</em></strong> back in 2015!</p><p></p><h4 id=\"the-rise-of-sri\">The rise of SRI</h4><p>It's no surprise that SRI has become a very popular technology. It's free to use, it's an open web standard supported across all browsers, it's unbelievably simple, and it provides invaluable protection against some quite serious threats. Helping to drive that adoption, all of the major CDN providers have been supplying their script tags with SRI attributes for years, it could have stopped some pretty serious attacks in recent history [<a href=\"https://scotthelme.co.uk/protect-site-from-cryptojacking-csp-sri/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>], and it's now even a recommended strategy in compliance standards like PCI DSS [<a href=\"https://scotthelme.co.uk/pci-dss-4-0-its-time-to-get-serious-on-magecart/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>]. If you have existing assets that don't have SRI protection, and you'd like to add it, you can use free tools like our <a href=\"https://report-uri.com/home/sri_hash?ref=scotthelme.ghost.io\" rel=\"noreferrer\">SRI Hash Generator</a> to take the URL of existing assets and create an SRI compatible script or style tag. But there lies a little bit of a problem. How do you know what assets that you have across your website that are eligible to use SRI, but aren't currently using it? </p><p></p><h4 id=\"integrity-policy-to-the-rescue\">Integrity Policy to the rescue!</h4><p>We now have a way to effortlessly audit all of the dependencies across your application to ensure that they're using SRI. Integrity Policy is an open web standard, requires no code or agent to be deployed, and has no negative impact to speak of. It can be enabled with a single HTTP Response Header:</p><p></p><pre><code>Integrity-Policy-Report-Only: blocked-destinations=(script), endpoints=(default)</code></pre><p></p><p>As you can see here, I'm using the <code>Report-Only</code> version of the header as it's best to start by gathering information before you consider any enforcing action. I'm setting the policy to monitor JavaScript and I'm instructing it to send telemetry to the <code>default</code> reporting endpoint. If you're not familiar with the <a href=\"https://scotthelme.co.uk/introducing-the-reporting-api-nel-other-major-changes-to-report-uri/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Reporting API</a> you can read my full blog post, but the summary is that you simply add a HTTP Response Header to let the browser know where to send the telemetry. This endpoint can also be used by a variety of different mechanisms so you can set it once and use it many times.</p><p></p><pre><code>Report-To: {\"group\":\"default\",\"max_age\":31536000,\"endpoints\":[{\"url\":\"https://helios.report-uri.com/a/t/g\"}],\"include_subdomains\":true}</code></pre><p></p><p>That's it! With both of those headers set, any time a browser loads one of your pages it will send an event to let you know if there is an asset being loaded without the use of SRI. As the Integrity Policy header was delivered in Report-Only mode, it will still allow the asset to load so there is no negative impact on your site. The idea here would be that you can now go and fix that problem by adding the integrity attribute to the asset, and then the events will no longer be sent.</p><p></p><h4 id=\"now-in-open-beta\">Now in open beta!</h4><p>Integrity Policy is now available on our site and is free to access for all customers during the open beta period. After announcing another brand new feature only a couple of months ago, <a href=\"https://scotthelme.co.uk/capture-javascript-integrity-metadata-using-csp/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">CSP Integrity</a>, these two features will form part of our new Integrity Suite offering that will become generally available in Q1 2026.</p><p>There's no doubt that these new capabilities are a fundamental improvement in client-side security, allowing for a level of native protection in the browser that is simply unmatched. Whilst the details we've covered in the CSP Integrity blog post, and now in this Integrity Policy blog post, are exciting, we still have a <strong><em>lot</em></strong> more to announce in the coming months!</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/11/integrity-policy.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "Integrity Policy",
"term": "Integrity Policy",
"url": null
},
{
"label": "javascript",
"term": "javascript",
"url": null
},
{
"label": "integrity",
"term": "integrity",
"url": null
},
{
"label": "Integrity Suite",
"term": "Integrity Suite",
"url": null
}
]
},
{
"id": "68e7a6e405c3450001b53f41",
"title": "CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded",
"description": "<p>We're very public and open about our infrastructure at <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a>, having written many blog posts about how we process billions of telemetry events every single week. As a result, it's no secret that we use Redis quite heavily across our infrastructure, and some have asked</p>",
"url": "https://scotthelme.ghost.io/cve-2025-49844-the-redis-cvss-10-0-vulnerability-and-how-we-responded/",
"published": "2025-10-14T08:11:47.000Z",
"updated": "2025-10-14T08:11:47.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp\" alt=\"CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded\"><p>We're very public and open about our infrastructure at <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a>, having written many blog posts about how we process billions of telemetry events every single week. As a result, it's no secret that we use Redis quite heavily across our infrastructure, and some have asked how the recent security vulnerability discovered in Redis has impacted us.</p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/10/report-uri.png\" class=\"kg-image\" alt=\"CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded\" loading=\"lazy\" width=\"946\" height=\"525\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/10/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/10/report-uri.png 946w\" sizes=\"(min-width: 720px) 720px\"></a></figure><h4 id=\"tldr\">TLDR;</h4><p>The short answer is that <a href=\"https://nvd.nist.gov/vuln/detail/CVE-2025-49844?ref=scotthelme.ghost.io\" rel=\"noreferrer\">CVE-2025-49844</a> hasn't impacted us at all, largely because of our strict stance on security and existing measures that we already had in place. That said, any time something big happens in the industry that might have affected us, we take a look at how we might improve even further.</p><p></p><h4 id=\"immediate-response\">Immediate response</h4><p>This advisory from Redis (<a href=\"https://github.com/redis/redis/security/advisories/GHSA-4789-qfc9-5f9q?ref=scotthelme.ghost.io\" rel=\"noreferrer\">GHSA-4789-qfc9-5f9q</a>) is the first visibility I had into the issue and the specified workaround was to use the ACL feature in Redis to restrict access to the commands that might be abused.</p><p></p><blockquote>An additional workaround to mitigate the problem without patching the redis-server executable is to prevent users from executing Lua scripts. This can be done using ACL to restrict EVAL and EVALSHA commands.</blockquote><p></p><p>This is an easy protection to implement and I know we don't use the <code>EVAL</code> or <code>EVALSHA</code> commands, so we could get this in place immediately and deploy it across the fleet via Ansible.</p><p></p><pre><code># Disable use of the EVAL and EVALSHA commands\nredis-cli ACL SETUSER {user} -EVAL -EVALSHA\n# Persist the change to config so it survives restarts\nredis-cli CONFIG REWRITE</code></pre><p></p><p>With that in place, we can take a little more time to investigate the issue and plan for the upgrade from Redis 8.2.1 to 8.2.2 to properly resolve the problem. </p><p></p><h4 id=\"upgrading-redis\">Upgrading Redis</h4><p>As fate would have it, ~2 months ago I published this blog post: <a href=\"https://scotthelme.co.uk/were-going-high-availability-with-redis-sentinel/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">We're going High Availability with Redis Sentinel!</a> In that blog post, I described our new High Availability setup using Redis Sentinel to give us failover capabilities between our new Primary and Replica caches. I also explained how one of the major benefits of the upgrade would be that updating our Redis server binary becomes a whole lot easier, and here we are!</p><p></p><figure class=\"kg-card kg-image-card kg-card-hascaption\"><img src=\"https://scotthelme.ghost.io/content/images/2025/10/image.png\" class=\"kg-image\" alt=\"CVE-2025-49844 - The Redis CVSS 10.0 vulnerability and how we responded\" loading=\"lazy\" width=\"562\" height=\"441\"><figcaption><span style=\"white-space: pre-wrap;\">Our Redis Sentinels fronting our Primary and Replica caches</span></figcaption></figure><p></p><p>With our new HA setup, it's simply a case of updating and upgrading the Replica cache, promoting it to Primary, and then updating what previously was the Primary cache but is now a Replica. Done!</p><pre><code>redis-cli info\n# Server\nredis_version:8.2.2\nredis_git_sha1:00000000\nredis_git_dirty:1\nredis_build_id:8ade872ac64c6931\nredis_mode:standalone\n...</code></pre><p></p><h4 id=\"existing-protections-and-investigations\">Existing protections and investigations</h4><p>Our Redis caches reside on a private network and are not at any time accessible from the public Internet. Further to that, we have firewall rules in place to only allow known IP addresses on our private network to have access to the Redis caches. Just to go the extra distance, we then only allow our application servers with the appropriate roles to have access to the appropriate Redis caches that they require to fulfil that role! Our network access is already locked down as tight as we can get it and there is no anomalous network activity in our logs.</p><p>Looking at the Redis caches themselves, we can use <code>commandstats</code> to get per-command counters on how many times each command has been called against the cache since the server started. To reset the counters it would require access to restart <code>redis-server</code> or call <code>CONFIG RESETSTAT</code>, and the command counters all look aligned with typical volumes before the update. Running the command, we can see no indication of the counts being reset, and there are no instances of <code>EVAL</code> or <code>EVALSHA</code> being called on the servers.</p><p></p><h4 id=\"further-hardening\">Further Hardening</h4><p>With some quick research looking at other powerful commands, I could see that we currently don't require the use of the <code>SCRIPT</code> or <code>FUNCTION</code> commands, so they presented themselves as another good opportunity to further reduce the commands available. </p><p></p><pre><code># Disable use of the SCRIPT and FUNCTION commands\nredis-cli ACL SETUSER {user} -SCRIPT -FUNCTION\n# Persist the change to config so it survives restarts\nredis-cli CONFIG REWRITE</code></pre><p></p><p>There are some other commands that I want to restrict, but they require further investigation to ensure there are no adverse effects. To wrap things up and make sure that all of these restrictions are working as intended, we can try to run some of the commands that should now be blocked.</p><p></p><pre><code>redis-cli EVAL \"return 1\" 0\n(error) NOPERM User default has no permissions to run the 'eval' command\nredis-cli ACL LOG 1\n1) 1) \"count\"\n 2) (integer) 1\n 3) \"reason\"\n 4) \"command\"\n 5) \"context\"\n 6) \"toplevel\"\n 7) \"object\"\n 8) \"eval\"\n 9) \"username\"\n 10) \"default\"\n 11) \"age-seconds\"\n 12) \"54.578\"\n 13) \"client-info\"\n 14) \"id=49 addr=*snip*:59092 laddr=*snip*:6379 fd=32 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=35 qbuf-free=20439 argv-mem=13 multi-mem=0 rbs=16384 rbp=16384 obl=0 oll=0 omem=0 tot-mem=37797 events=r cmd=eval user=default redir=-1 resp=2 lib-name= lib-ver= io-thread=0 tot-net-in=35 tot-net-out=0 tot-cmds=0\"\n 15) \"entry-id\"\n 16) (integer) 0\n 17) \"timestamp-created\"\n 18) (integer) 1760018943783\n 19) \"timestamp-last-updated\"\n 20) (integer) 1760018943783</code></pre><p></p><p>The <code>EVAL</code> command was blocked as expected and the <code>ACL LOG</code> now has an entry to show that the block happened, which makes it easier to track the attempted usage of blocked commands in the future too.</p><p>With the fleet now updated to Redis 8.2.2, the additional protections in place, and no indication that we (or anyone else) were affected, I'm happy to consider this resolved!</p><p></p><h4 id=\"thats-a-lot-of-data\">That's a lot of data!</h4><p>If you're curious just how much data we process, you can see my recent blog post <a href=\"https://scotthelme.co.uk/trillion-with-a-t-surpassing-2-trillion-events-processed/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀</a> which covers the details. That blog post also announced the launch of our public dashboard that provides a live insight into our telemetry volumes, along with heaps of other interesting data, so check that out here too: <a href=\"https://dash.report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Public Dashboard</a></p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/10/9b5f57d6-6615-4e2a-b94c-4ee23a4cf06e.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "Redis",
"term": "Redis",
"url": null
}
]
},
{
"id": "68bfe86ad26c1a00012b4a80",
"title": "Capture JavaScript Integrity Metadata using CSP!",
"description": "<p>Today we're announcing the open beta of a brand new and incredibly powerful feature on the Report URI platform, CSP Integrity! Having the ability to collect integrity metadata for scripts running on your site opens up a whole new realm of possibilities, and it couldn't be</p>",
"url": "https://scotthelme.ghost.io/capture-javascript-integrity-metadata-using-csp/",
"published": "2025-09-29T10:51:09.000Z",
"updated": "2025-09-29T10:51:09.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp\" alt=\"Capture JavaScript Integrity Metadata using CSP!\"><p>Today we're announcing the open beta of a brand new and incredibly powerful feature on the Report URI platform, CSP Integrity! Having the ability to collect integrity metadata for scripts running on your site opens up a whole new realm of possibilities, and it couldn't be simpler to get started. If you have a few seconds spare, you have enough time to get started now!</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/09/report-uri.png\" class=\"kg-image\" alt=\"Capture JavaScript Integrity Metadata using CSP!\" loading=\"lazy\" width=\"946\" height=\"525\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/09/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/09/report-uri.png 946w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><h4 id=\"a-revolution-for-content-security-policy\">A revolution for Content Security Policy</h4><p>Content Security Policy (CSP) is an incredibly powerful mechanism, allowing you to leverage control over a wide range of resources on your site. You can control where resources are loaded from or where data is sent to, and you can be alerted when things don't go according to plan. But regular readers will already know all of the existing benefits of CSP, so today, let's focus on the latest addition to the CSP arsenal; the collection of integrity metadata.</p><p>It's so easy to get started that we can boil it down to two simple steps. </p><ol><li>Ask the browser to send integrity metadata for scripts.</li><li>Tell it where to send the integrity metadata for scripts.</li></ol><p></p><p>That's really all there is to it, so let's break it down. If you already have a CSP in place, you will need to add the <code>'report-sha256'</code> keyword to your <code>script-src</code> directive, and if you don't have a CSP in place, you can add one with just this directive. This is what your CSP <code>script-src</code> might look like after you add the new keyword, which I've highlighted in bold:</p>\n<!--kg-card-begin: html-->\n<pre>script-src 'self' some-cdn.com <b>'report-sha256'</b>;</pre>\n<!--kg-card-end: html-->\n<p></p><p>Now that you've added the new keyword to your policy, the browser will send integrity metadata for scripts that load on your site, but you will need to tell the browser where to send it, and for that, we will use the <a href=\"https://scotthelme.co.uk/introducing-the-reporting-api-nel-other-major-changes-to-report-uri/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Reporting API</a>. All you have to do to enable this is add a new response header:</p><pre><code>Report-To: {\"group\":\"default\",\"max_age\":31536000,\"endpoints\":[{\"url\":\"https://scotthelme.report-uri.com/a/d/g\"}],\"include_subdomains\":true}</code></pre><p></p><p>This defines a new group called <code>default</code> and sets the destination for telemetry data to <code>https://scotthelme.report-uri.com/a/d/g</code> . Please note that you should copy this value from the Setup page in <em>your</em> Report URI account as it will be unique to you and the one shown here is my unique URL for demonstration purposes. </p><p>With that, we can now link the two together by adding the <code>report-to</code> directive to your CSP:</p>\n<!--kg-card-begin: html-->\n<pre>script-src 'self' some-cdn.com 'report-sha256'; <b>report-to default;</b></pre>\n<!--kg-card-end: html-->\n<p></p><p>All you have to do now is sit back and wait for the telemetry to start rolling in!</p><p></p><h4 id=\"surfacing-this-in-the-ui\">Surfacing this in the UI</h4><p>Processed in the same way that any telemetry sent to us would be, this data will only take a few minutes to start showing up in your dashboard. I've used our 'aggregate' search filter here to grab a list of unique dependencies that have been loaded on our site, and we can start to see the value we can extract from this data.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/09/image.png\" class=\"kg-image\" alt=\"Capture JavaScript Integrity Metadata using CSP!\" loading=\"lazy\" width=\"1531\" height=\"870\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/09/image.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/09/image.png 1000w, https://scotthelme.ghost.io/content/images/2025/09/image.png 1531w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>If we focus on just one of these entries, we can see that a Bootstrap JS file has been loaded.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/09/image-1.png\" class=\"kg-image\" alt=\"Capture JavaScript Integrity Metadata using CSP!\" loading=\"lazy\" width=\"525\" height=\"97\"></figure><p></p><p>The browser has reported that the hash of the asset is <code>sha256-wMCQIK229gKxbUg3QWa544ypI4OoFlC2qQl8Q8xD8x8=</code>, which is helpful in various different ways. If we were loading this file from an external location for example, we could now track the hash over time to see if the file we are being served has changed. We can also use the hash to try and identify if the file we were served is the file we were expecting.</p><p></p><h4 id=\"identifying-verified-javascript-files\">Identifying verified JavaScript files</h4><p>As you can see from the screenshot above, we have identified that the particular file loaded was a verified file from the 'bootstrap' package, hence the green marker with the library name. Using the hash as a fingerprint to uniquely identify a file is a common approach, but you need to have a reliable and <em>verified</em> library of fingerprints to reference against in order to lookup the fingerprint and know for sure. This is something that we've been working hard to build and so far we have verified fingerprints for <strong><em>4,268,847</em></strong> unique JS files from common packages, libraries and dependencies across the Web! As we have the fingerprints for each of those files stored in their sha256, sha 384 and sha512 variants, it means we have a running total of <strong><em>12,806,541</em></strong> fingerprints in our database so far! This is a huge amount of valuable information to draw from, and it's something that's available right there in the dashboard.</p><p>That's not all, though, there's something even better to add!</p><p></p><h4 id=\"identifying-javascript-vulnerabilities\">Identifying JavaScript vulnerabilities</h4><p>You may notice that the marker for that file is green, and that means that there are no known issues reported with that particular version of that file/library. If you keep scrolling through your data, you might, if you're unlucky, come across a file that has a red marker instead...</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/09/image-2.png\" class=\"kg-image\" alt=\"Capture JavaScript Integrity Metadata using CSP!\" loading=\"lazy\" width=\"525\" height=\"95\"></figure><p></p><p>This particular file is from a version of the Bootstrap library that has a known security vulnerability, and we can now surface that information directly to you in our UI. If you click the warning, it will open a small dialog box to give you some quick information on what the minimum version of the library you need to upgrade to is, and it will even link out to the disclosure notice for the vulnerability.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/09/image-3.png\" class=\"kg-image\" alt=\"Capture JavaScript Integrity Metadata using CSP!\" loading=\"lazy\" width=\"527\" height=\"223\"></figure><p></p><p>This will be an awesome feature to have, allowing you to keep a close eye on exactly how problematic your JS dependencies are, and potentially avoiding costly issues from using dependencies with known vulnerabilities. </p><p></p><h4 id=\"looking-at-the-new-telemetry-payload\">Looking at the new telemetry payload</h4><p>Defined in the specification as a <code>csp-hash</code> report, it has a very familiar JSON payload, similar to your typical CSP violation report. Here's a sample of one of our payloads:</p><p></p><pre><code>{\n \"csp-hash\": {\n \"destination\": \"script\",\n \"documentURL\": \"https://report-uri.com/login/\",\n \"hash\": \"sha256-wMCQIK229gKxbUg3QWa544ypI4OoFlC2qQl8Q8xD8x8=\",\n \"subresourceURL\": \"https://cdn.report-uri.com/libs/refresh/bootstrap/bootstrap.bundle.min.js\",\n \"type\": \"subresource\"\n }\n}</code></pre><p></p><p>Breaking the fields inside this payload down, it's fairly self-explanatory:</p><p><code>destination</code> - Only <code>script</code> is supported for now, with new destinations being planned for future updates.</p><p><code>documentURL</code> - This is the URL the browser was visiting when the reported resource was observed.</p><p><code>hash</code> - This is the hash of the resource, which in this case is the sha256 hash as that is what we requested.</p><p><code>subresourceURL</code> - This is the URL of the script that was requested and the hash is of the content of this file.</p><p><code>type</code> - Only <code>subresource</code> is supported for now, with the ability to expand to new types in the future.</p><p></p><p>Now, with the <code>'report-sha256'</code> keyword in place, one of these payloads will be sent for each script that loads on your page!</p><p></p><h4 id=\"quick-tips\">Quick Tips</h4><p>I'm sure many people will have some questions that spring to mind after reading this, so let me address some of the most common ones that I encountered during our closed beta testing. </p><p></p><h6 id=\"do-i-need-to-build-a-full-csp\">Do I need to build a full CSP?</h6><p>No! You don't need any CSP at all, you can start monitoring scripts and collecting integrity metadata right away:</p><pre><code>Content-Security-Policy-Report-Only: script-src 'report-sha256'; report-to default</code></pre><p></p><h6 id=\"is-integrity-metadata-sent-for-scripts-that-load-or-scripts-that-are-blocked\">Is integrity metadata sent for scripts that load, or scripts that are blocked?</h6><p>Integrity metadata is only sent for scripts that load on your page. If a script is blocked, it is not requested, so there is no file to provide integrity metadata for.</p><p></p><h6 id=\"will-this-use-a-lot-of-my-event-quota\">Will this use a lot of my event quota?</h6><p>No, we're going to downsample CSP Integrity reports for all customers at a rate of 1/10, so whilst it will of course use some event quota, it should not use excessive amounts.</p><p></p><h6 id=\"what-happens-after-the-open-beta\">What happens after the open beta?</h6><p>CSP Integrity will become an add-on feature for customers on our Ultimate plan and customers on an Enterprise plan in 2026.</p><p></p><h4 id=\"get-started-now\">Get started now</h4><p>I will be announcing a webinar to go through the new CSP Integrity feature so keep an eye out for that, but everything you need to get started is right here. It really is as simple as adding those two new values to your CSP and just waiting for the valuable data to start rolling in!</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/09/csp-integrity-1.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "CSP",
"term": "CSP",
"url": null
},
{
"label": "integrity",
"term": "integrity",
"url": null
},
{
"label": "javascript",
"term": "javascript",
"url": null
}
]
},
{
"id": "68976e9629f3b600011277d4",
"title": "We're going High Availability with Redis Sentinel!",
"description": "<p>We've just deployed some mega updates to our infrastructure at Report URI that will give us much more resilience in the future, allow us to apply updates to our servers even faster, and will probably go totally unnoticed from the outside!</p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/report-uri.png\" class=\"kg-image\" alt loading=\"lazy\" width=\"946\" height=\"525\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/08/report-uri.png 946w\" sizes=\"(min-width: 720px) 720px\"></a></figure><h4 id=\"our-previous-redis-setup\">Our previous Redis setup</h4><p>I've</p>",
"url": "https://scotthelme.ghost.io/were-going-high-availability-with-redis-sentinel/",
"published": "2025-08-18T12:46:41.000Z",
"updated": "2025-08-18T12:46:41.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp\" alt=\"We're going High Availability with Redis Sentinel!\"><p>We've just deployed some mega updates to our infrastructure at Report URI that will give us much more resilience in the future, allow us to apply updates to our servers even faster, and will probably go totally unnoticed from the outside!</p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/report-uri.png\" class=\"kg-image\" alt=\"We're going High Availability with Redis Sentinel!\" loading=\"lazy\" width=\"946\" height=\"525\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/report-uri.png 600w, https://scotthelme.ghost.io/content/images/2025/08/report-uri.png 946w\" sizes=\"(min-width: 720px) 720px\"></a></figure><h4 id=\"our-previous-redis-setup\">Our previous Redis setup</h4><p>I've talked about our infrastructure openly before, but I will give a brief overview of what we were working with and why I wanted to upgrade. We were using Redis in two places, one instance for our application session store, which is fairly self explanatory, and one instance for our inbound telemetry cache. Both of these isolated instances of Redis have been upgraded in the same way, but I will focus on the telemetry cache as this is the one that faces a significant amount of load! Here's how it works.</p><p>Visitors head to the websites of our customers, and they may or may not send some telemetry to us depending on whether there are any security, performance or other concerns to report to us about the website they were visiting. These telemetry events are received and are placed directly in to Redis because there are simply too many for us to process them in real-time. Our servers, that we refer to as 'consumers', will then feed telemetry from this Redis cache and process it, applying normalisation, filtering, checking it against our Threat Intelligence, and doing a whole heap of other work, before placing it in persistent storage, at which point it becomes available to customers in their dashboards. This 'ingestion pipeline' can take anywhere from 20-30 seconds during quiet periods, and spike up to 6-7 minutes during our busiest periods, but it is reliable and consistent. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-10.png\" class=\"kg-image\" alt=\"We're going High Availability with Redis Sentinel!\" loading=\"lazy\" width=\"883\" height=\"187\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-10.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-10.png 883w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>If you'd like to see some real data about the volumes of telemetry we process, what our peaks and troughs look like, and a whole bunch of other interesting metrics, you should absolutely check out our <a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Public Dashboard</a>. That has everything from telemetry volumes, client types, regional traffic patterns, a global heat map and even a Pew Pew Map!!</p><p></p><h4 id=\"a-single-point-of-failure\">A single point of failure</h4><p>Both of our caches, the session cache and the telemetry cache, were single points of failure, and it's never a good idea to have a single point of failure. We've never had an issue with either of them, but it's been something I've wanted to address for a while now. Not only that, but it makes updating and upgrading them much harder work too. The typical process for our upgrades has been to bring up a new server, fully patch and update it and then deploy Redis along with our configuration, with the final step being a data migration consideration. Using Ansible to do the heavy lifting makes this process easier, but not easy. The session cache of course needs a full data migration and then the application servers can flip from the old one to the new one, which isn't so bad because the data is relatively small. The telemetry cache is a little trickier because it involves pointing the inbound telemetry to the new cache, while allowing the consumers to drain down the old telemetry cache before moving them over too. As I say, it's not impossible, but it could be a lot better, and we still have that concern of them both being a single point of failure. </p><p></p><h4 id=\"redis-sentinel-to-the-rescue\">Redis Sentinel to the rescue!</h4><p>You can read the official docs on <a href=\"https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Redis Sentinel</a> but I'm going to cover everything you need to know here if you'd like to keep reading. Redis Sentinel provides a few fundamental features that we're now leveraging to provide a much more resilient service. </p><p><strong>Monitoring:</strong> Redis Sentinels can monitor a pool of Redis caches to determine if they are healthy and available. </p><p><strong>Failover:</strong> Redis Sentinels will promote and demote Redis caches between the roles of Primary and Replica based on their health. </p><p><strong>Configuration:</strong> Clients connect to a Redis Sentinel to get the ip/port for the current Primary cache.</p><p></p><p>Leveraging these features, we've now created a much more robust solution that looks like this:</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-8.png\" class=\"kg-image\" alt=\"We're going High Availability with Redis Sentinel!\" loading=\"lazy\" width=\"1317\" height=\"1034\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-8.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-8.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-8.png 1317w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>To give an overview of what's happening here, I will break it down in to the following:</p><p><strong>Redis Caches:</strong> These have an assigned role of either Primary or Replica, and for the purposes of our diagram, the red one will be the Primary and the yellow one will be the Replica. If you have more than two caches in your pool, there would be more Replicas. The role of the Replica is to keep itself fully aligned with the Primary so it can become the Primary at any point if needed. The role of the Primary is to serve the clients as their Redis cache and is where our inbound telemetry is being sent.</p><p><strong>Redis Sentinels:</strong> These severs do a few things, so let's break it down.</p><ol><li>They're providing ip/port information to clients about which cache is the current Primary. Clients don't know about the caches or how many there are, nor do they know which one is the Primary, so they first ask a Sentinel which cache is the current Primary and then connect to that one. Note that no cache activity passes through the Sentinels, all they do is provide the information to the client on where to connect, and the client will then connect directly to the cache.</li><li>They're monitoring the health of the Primary and Replica/s to detect any issues. If a problem is detected with the Primary, it is put to a vote to see if a Replica should be promoted to Primary. Our configuration requires a quorum of 3/4 votes to successfully promote a Replica to Primary, and demote the Primary to a Replica when it returns. Once that happens, the Sentinels will reconfigure all caches to follow the new Primary, and start providing the new Primary ip/port information to clients.</li><li>They're always monitoring for the addition of new caches or Sentinels. There is no configuration for how many Sentinels or caches there are, they are discovered by talking to the current Primary and getting information on how many Replicas and Sentinels are connected to that Primary.</li></ol><p></p><h4 id=\"failing-over\">Failing over</h4><p>Of course, all of this work is designed to give us a much more resilient solution, and it will also make our work for installing updates and upgrades much easier too. There are two scenarios when the Sentinels can failover from a Primary to a Replica, and they are when the Sentinels detect an issue with the Primary and vote to demote it, or when we manually trigger a failover.</p><p>When we're getting ready to do some upgrades, the first step will be to take the Replica down so the Sentinels notice and remove it as an option for a current failover. We can then update/upgrade that server and bring it back online as a viable Replica. Now, when we want to upgrade the cache that is the current Primary, we trigger the failover and the Sentinels will take care of it, meaning our infrastructure now looks like this with the new roles.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-9.png\" class=\"kg-image\" alt=\"We're going High Availability with Redis Sentinel!\" loading=\"lazy\" width=\"1308\" height=\"1032\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-9.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-9.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-9.png 1308w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>If there is a problem with the update/upgrade, we can immediately fail back, giving us a nice recovery option, and if everything goes well, we can now follow the same process of taking the Replica down for updates/upgrades. Once that's done, both servers are fully updated with considerably less work than before!</p><p></p><h4 id=\"other-considerations\">Other considerations</h4><p>Whilst working through this project there were a few little details that I picked up along the way that might be useful to share, and also some that are probably a bit more specific to our deployment. </p><ul><li>Our Redis Sentinels are running on dedicated servers, but I saw many recommendations to co-locate a Redis Sentinel service alongside the Redis Server service on the cache servers themselves. I decided against this for a couple of reasons. First, I didn't want to lose a Sentinel when we took a cache down for updates/upgrades because this will impact the total number of Sentinels available to clients, and it has implications for voting too. Second, I wanted to take that load away from the caches so they could focus on being caches, meaning each of our servers is focusing on doing one thing well.</li><li>Your quorum for voting must be >50% of the number of Sentinels you have, it must be a majority vote to promote a new Primary. With our four Sentinels, if we had a quorum of two, we have less protection against something called a Split-Brain happening. As an example, let's say we have two caches, Redis Cache 1 and Redis Cache 2. Two of our four Sentinels have a transient network issue and see the Primary (Redis Cache 1) as being down, so they vote between themselves to promote a Replica (Redis Cache 2), and then begin that process. Because they can't communicate with the previous Primary (Redis Cache 1), they can't demote it to a Replica. Meanwhile, the other two Sentinels see no issue and continue as normal with their existing Primary (Redis Cache 1). You now have Sentinels providing ip/port information for two different Primaries that are both accepting writes from clients and their data is now diverging, a Split-Brain! Having a quorum require a majority of your Sentinels to vote doesn't fully solve this issue, but it does give you a lot more protection against it.</li><li>If you have a very read-heavy workload, your Replicas can share the load as they do support read-only connections. In our two scenarios we can't really leverage this benefit, which is a real shame, but being able to distribute reads across your replicas may be a huge benefit to you.</li></ul><p></p><h4 id=\"nobody-would-know\">Nobody would know!</h4><p>Whilst this was quite a huge change on our side, from the outside, nobody would know anything about this if I hadn't published this blog post! Going forwards, this will allow me to sleep a lot better knowing we have a more resilient service, and that our future updates and upgrades will now take much less time and effort than before. If you have any feedback or suggestions on our deployment, feel free to drop by the comments below. </p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/08/redis-sentinel-upgrade.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "Redis",
"term": "Redis",
"url": null
},
{
"label": "Sentinel",
"term": "Sentinel",
"url": null
},
{
"label": "High Availability",
"term": "High Availability",
"url": null
}
]
},
{
"id": "68974a0c29f3b6000112773f",
"title": "Automation improvements after a Tesla Powerwall outage!",
"description": "<p>So, a weird thing happened over the last couple of days, and my Tesla Powerwalls weren't working properly, or, at all, actually... What's even more strange is that Tesla has been completely silent about this and hasn't made a single announcement about the issue</p>",
"url": "https://scotthelme.ghost.io/automation-improvements-after-a-tesla-powerwall-outage/",
"published": "2025-08-11T09:49:48.000Z",
"updated": "2025-08-11T09:49:48.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp\" alt=\"Automation improvements after a Tesla Powerwall outage!\"><p>So, a weird thing happened over the last couple of days, and my Tesla Powerwalls weren't working properly, or, at all, actually... What's even more strange is that Tesla has been completely silent about this and hasn't made a single announcement about the issue that I can find, and I haven't been notified.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-1.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"600\" height=\"211\" srcset=\"https://scotthelme.ghost.io/content/images/2025/08/image-1.png 600w\"></figure><p></p><h4 id=\"my-home-setup\">My Home Setup</h4><p>You can read various posts on my blog about how I automate almost all of my home with Home Assistant, and more recently there were two Tesla Powerwall posts [<a href=\"https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">1</a>][<a href=\"https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">2</a>] about bringing the final pieces of the puzzle together. I have everything working almost exactly how I want it to, with the one outstanding question on how I can control the charge rate of the Powerwalls, which I will be answering later!</p><p>First, though, this recent Tesla outage that I've seen absolutely nothing from Tesla about... Regular readers will know I automate my Powerwalls via <a href=\"https://www.home-assistant.io/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Home Assistant</a> using the <a href=\"https://teslemetry.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Teslemetry</a> service. </p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://teslemetry.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-2.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"731\" height=\"76\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-2.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-2.png 731w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><p>I can control when the Powerwalls are charging or not based on the current cost for electricity import, and I can also control when they're exporting to the grid if I have excess capacity for the day. Things were going really quite well, until I woke up one morning and the batteries were basically empty. I woke up on the morning of the 7th August and we hadn't charged the batteries in the cheap off-peak tariff that we get overnight, which is really the main point of the batteries. Fill up on the cheap stuff overnight, avoid the expensive stuff during the day.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-3.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"485\" height=\"345\"></figure><p></p><p>I tried to enable grid charging to see if I could just get some power back in to the batteries so they weren't sat on such a low SoC, but it wouldn't enable. I wondered if Teslemetry were having an issue, but nothing was reported on their side. I disabled the integration and went directly to the official Tesla app and tried to enable grid charging there, but again, it wouldn't enable. After some searching, it seems it wasn't just me having the issue and anyone with a Powerwall setup seemed to be having the exact same problem. Shortly after, an incident went up on the Teslemetry <a href=\"https://status.teslemetry.com/incident/701944?ref=scotthelme.ghost.io\" rel=\"noreferrer\">status page</a> saying that the issue was with the Tesla API.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-4.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"834\" height=\"517\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-4.png 600w, https://scotthelme.ghost.io/content/images/2025/08/image-4.png 834w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>It wasn't just Teslemetry, either. Other services that also allow for control of Tesla Powerwalls were having issues and reporting similar problems on their status pages too, like <a href=\"https://docs.netzero.energy/docs/tesla/2025-08/GridChargingIssue.html?ref=scotthelme.ghost.io\" rel=\"noreferrer\">NetZero</a>.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/image-5.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"1003\" height=\"398\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/08/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/08/image-5.png 1000w, https://scotthelme.ghost.io/content/images/2025/08/image-5.png 1003w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>As you can see from the graph above, I did manage to capture some excess solar production during the day and charge the batteries a little, but it was almost 24 hours later when the issue finally resolved and the button to enable grid charging started magically working again! I enabled the Teslemetry integration and we were back in business. </p><p></p><h4 id=\"so-whats-up-with-that\">So what's up with that?..</h4><p>I genuinely do find it staggering that a trillion dollar company can't notify customers who are actively using their product of an issue such as this! It's pretty crazy when you think about it that there was an issue that effectively rendered Powerwalls useless, and there have been no notifications, updates, or a post-mortem, nothing... This is really poor from Tesla and I think this issue deserves more attention.</p><p>Part of me is also wondering if there might be a problem with enabling a huge amount of Powerwalls to be able to grid charge again all at once. Could it have an impact on the grid? Was that part of the problem and they staggered the rollout of the fix? What was the problem? I just want to know! </p><p><strong><em>Update 11th Aug 15:17 BST:</em></strong> Tesla responded to my support ticket with the following:</p><blockquote>We are reaching out regarding your inquiry that grid charging turns it self off automatically. Our App Team is aware of this issue and is currently working on resolving it.<br>Please rest assured, we will resolve it within the next Tesla App updates.</blockquote><p></p><p></p><h4 id=\"controlling-how-fast-your-powerwalls-charge\">Controlling how fast your Powerwalls charge</h4><p>One of the last things I had to address in my previous blog post was controlling how fast my Powerwalls were charging. Once you set them to charge, they charge up at full power, which isn't really necessary. Charging them at such a high rate isn't necessary as I have a long time to charge them, and charging at a higher rate is worse for the battery, and it leaves it sat at 100% for a longer period overnight, which is also worse for the battery! Overall that's a bad pair of options, but what if you could change the rate at which the batteries draw power when charging? Well, you can!</p><p>This is super easy to do thanks to my use of Home Assistant and Teslemetry, and it's worth pointing out that one of the options below won't be available to you if you're not using a service like Teslemetry as it's not available in the Tesla app! It turns out that when the batteries are in a different mode, they charge at a different rate. Here is the operation mode you need to set in Teslemetry, and in brackets is the name of that mode in the Tesla app.</p><p></p><table>\n<thead>\n<tr>\n<th>Operation Mode</th>\n<th>Total Draw</th>\n<th>Per Battery</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Autonomous (Time-Based Control)</td>\n<td>15kW</td>\n<td>5kW</td>\n</tr>\n<tr>\n<td>Self-consumption (Self-Powered)</td>\n<td>5.7kW</td>\n<td>1.9kW</td>\n</tr>\n<tr>\n<td>Backup (not available)</td>\n<td>11.1kW</td>\n<td>3.7kW</td>\n</tr>\n</tbody>\n</table>\n<p></p><p>I have 3 x Powerwalls so the above numbers show my total grid power draw when charging, and what the breakdown is per Powerwall. I've always been using \"Autonomous\" mode, which is known as \"Time-Based Control\" in the Tesla app, and that results in the batteries charging at their maximum rate of 5kW each!</p><p>It turns out that if you put the Powerwalls in \"Self-consumption\" mode, which is known as \"Self-Powered\" in the Tesla app, they only charge at 1.9kW each. That might be helpful to you, but for me, it's not enough. The Powerwalls have an official usable capacity of 13.5kWh, and mine are reporting a usable capacity of either 13.7kWh or 13.8kWh via the API. My cheap rate tariff applies from 23:30 to 05:30 giving me 6 hours and unfortunately, 6 hours x 1.9kW = 11.4kWh, which is not enough to fully recharge them.</p><p>The good news is that because I'm using the Teslemetry integration, it gives me access to the \"Backup\" mode, which isn't available in the Tesla app, and it sees the batteries charge at ~3.7kW, which works out quite well because 13.8kWh / 3.7kW = 3.7 hours (3hrs 45min). That means the batteries can charge at a lower rate and take a longer time to hit 100% SoC, but still be comfortably ready by 05:30 even if I need to go from 0% to 100%. Another bonus is that Backup mode will automatically set the Backup Reserve to 100% too! For comparison, here is the charging curve before and after the change, showing that slightly gentler curve now I'm using Backup mode.</p><p></p><figure class=\"kg-card kg-image-card kg-card-hascaption\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/Screenshot-2025-08-09-152600.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"477\" height=\"343\"><figcaption><span style=\"white-space: pre-wrap;\">Batteries charging at 15kW</span></figcaption></figure><p></p><figure class=\"kg-card kg-image-card kg-card-hascaption\"><img src=\"https://scotthelme.ghost.io/content/images/2025/08/Screenshot-2025-08-09-152614.png\" class=\"kg-image\" alt=\"Automation improvements after a Tesla Powerwall outage!\" loading=\"lazy\" width=\"475\" height=\"342\"><figcaption><span style=\"white-space: pre-wrap;\">Batteries charging at 11.1kW</span></figcaption></figure><p></p><h4 id=\"my-current-automations\">My current automations</h4><p>Given the above issue with the Tesla API, I wanted to avoid turning Grid Charging on and off because I might find myself in a position where I can't turn it back on again at some point in the future... Going forwards, I'm going to control charging by toggling the operation mode between Autonomous and Backup, whilst setting the Backup Reserve to the appropriate level too. I will also provide a copy of my export automation for completeness too. Here is the battery charging automation:</p><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Dynamic Battery Charging Control\ndescription: |\n Sets Shireburn operation mode and backup reserve based on price and SoC.\n - Cheap & below target SoC => backup @ 100%\n - Expensive or above target SoC => autonomous @ target SoC\ntriggers:\n - trigger: time_pattern\n minutes: /5\nconditions: []\nactions:\n - choose:\n - conditions:\n - condition: numeric_state\n entity_id: >-\n sensor.octopus_energy_electricity_snip_current_rate\n below: input_number.electricity_cost_threshold\n - condition: numeric_state\n entity_id: sensor.shireburn_charge\n below: input_number.powerwall_soc_threshold\n sequence:\n - condition: or\n conditions:\n - condition: template\n value_template: \"{{ states('select.shireburn_operation_mode') != 'backup' }}\"\n - condition: template\n value_template: \"{{ states('number.shireburn_backup_reserve') | int != 100 }}\"\n - action: select.select_option\n target:\n entity_id: select.shireburn_operation_mode\n data:\n option: backup\n - action: number.set_value\n target:\n entity_id: number.shireburn_backup_reserve\n data:\n value: 100\n - conditions:\n - condition: numeric_state\n entity_id: >-\n sensor.octopus_energy_electricity_snip_current_rate\n above: input_number.electricity_cost_threshold\n sequence:\n - condition: or\n conditions:\n - condition: template\n value_template: >-\n {{ states('select.shireburn_operation_mode') != 'autonomous'\n }}\n - condition: template\n value_template: \"{{ states('number.shireburn_backup_reserve') | int != 0 }}\"\n - action: select.select_option\n target:\n entity_id: select.shireburn_operation_mode\n data:\n option: autonomous\n - action: number.set_value\n target:\n entity_id: number.shireburn_backup_reserve\n data:\n value: 0\nmode: single\n</code></pre><p></p><p>Here is the export automation:</p><p></p><pre><code class=\"language-yaml\">alias: Dynamic Battery Export Control\ndescription: >-\n Adjust export setting and export mode display based on battery SoC and\n time-of-day\ntriggers:\n - minutes: /5\n trigger: time_pattern\nactions:\n - variables:\n now_ts: \"{{ now().timestamp() }}\"\n today: \"{{ now().date().isoformat() }}\"\n start_ts: \"{{ (today ~ 'T05:30:00') | as_datetime | as_timestamp }}\"\n end_ts: \"{{ (today ~ 'T23:30:00') | as_datetime | as_timestamp }}\"\n end_for_target_ts: \"{{ (today ~ 'T23:15:00') | as_datetime | as_timestamp }}\"\n current_soc: \"{{ states('sensor.shireburn_charge') | float(0) }}\"\n - choose:\n - conditions:\n - condition: template\n value_template: \"{{ now_ts < start_ts or now_ts > end_ts }}\"\n sequence:\n - target:\n entity_id: input_number.target_soc\n data:\n value: 100\n action: input_number.set_value\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: No Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'never' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: never\n action: select.select_option\n default:\n - variables:\n total_secs: \"{{ end_for_target_ts - start_ts }}\"\n elapsed_secs: \"{{ now_ts - start_ts }}\"\n progress: >-\n {{ (elapsed_secs / total_secs) if elapsed_secs < total_secs else 1.0\n }}\n target_soc: \"{{ 100 - (95 * (progress ** 1.5)) }}\"\n - target:\n entity_id: input_number.target_soc\n data:\n value: \"{{ target_soc | round(0) }}\"\n action: input_number.set_value\n - choose:\n - conditions:\n - condition: template\n value_template: |\n {% set hour = now().hour %} {% if hour < 23 %}\n {{ current_soc > (target_soc + 5) }}\n {% else %}\n {{ current_soc > target_soc }}\n {% endif %}\n sequence:\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: Battery + Solar Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'battery_ok' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: battery_ok\n action: select.select_option\n - conditions:\n - condition: template\n value_template: \"{{ current_soc < (target_soc - 5) }}\"\n sequence:\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: No Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'never' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: never\n action: select.select_option\n default:\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: Solar Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'pv_only' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: pv_only\n action: select.select_option\nmode: single\n</code></pre><p></p><p>Hopefully they will be useful and perhaps we might know more about the Tesla API outage soon!..</p><p></p><p></p>\n<!--kg-card-begin: html-->\n<style>\n pre[class*=\"language-\"],\n code[class*=\"language-\"] {\n font-size: 0.85em !important;\n}\n</style>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css\" integrity=\"sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\">\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js\" integrity=\"sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js\" integrity=\"sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<!--kg-card-end: html-->",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/08/header-tesla-powerwall-debugging.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Tesla Powerwall",
"term": "Tesla Powerwall",
"url": null
},
{
"label": "Home Assistant",
"term": "Home Assistant",
"url": null
},
{
"label": "Teslemetry",
"term": "Teslemetry",
"url": null
}
]
},
{
"id": "684ffc0ea1c29300018cdf43",
"title": "OWASP ASVS 5.0.0 is here!",
"description": "<p>I've been a huge fan of OWASP for a very long time, having spoken at their conferences, contributed to their projects, consumed many of their resources and met some really awesome people along the way! Just recently, one of the very popular OWASP projects, the <a href=\"https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Application Security Verification</a></p>",
"url": "https://scotthelme.ghost.io/owasp-asvs-5-0-0-is-here/",
"published": "2025-08-04T10:07:02.000Z",
"updated": "2025-08-04T10:07:02.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg\" alt=\"OWASP ASVS 5.0.0 is here!\"><p>I've been a huge fan of OWASP for a very long time, having spoken at their conferences, contributed to their projects, consumed many of their resources and met some really awesome people along the way! Just recently, one of the very popular OWASP projects, the <a href=\"https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Application Security Verification Standard</a> (ASVS), had a major update released and it brought with it some exciting changes. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/image-5.png\" class=\"kg-image\" alt=\"OWASP ASVS 5.0.0 is here!\" loading=\"lazy\" width=\"2000\" height=\"500\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/06/image-5.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/image-5.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/06/image-5.png 1600w, https://scotthelme.ghost.io/content/images/size/w2400/2025/06/image-5.png 2400w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><h4 id=\"owasp-asvs\">OWASP ASVS</h4><p>I'd bet that most people are familiar with the <a href=\"https://owasp.org/www-project-top-ten/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">OWASP Top 10</a> project, one of the most notable flagship projects to come out of OWASP! </p><blockquote>The OWASP Top 10 is a standard awareness document for developers and web application security. It represents a broad consensus about the most critical security risks to web applications.</blockquote><p></p><p>For me, coming in at a close second place is the <a href=\"https://owasp.org/www-project-application-security-verification-standard/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">OWASP ASVS</a> project, another project aimed at helping the community with a structured approach to taking care of application security.</p><blockquote>The OWASP Application Security Verification Standard (ASVS) Project provides a basis for testing web application technical security controls and also provides developers with a list of requirements for secure development.</blockquote><p></p><p>One of the things I love about the ASVS is the straightforward, \"no nonsense\" approach. They aren't requirements imagined in a vacuum, beyond the realistic hope or requirement of anyone trying to implement, instead they seek to be effective and balanced. This quote from the early paragraphs of the document in the 'Level evaluation' section, where you consider which level of compliance is appropriate for you, says a lot:</p><blockquote>Levels are defined by priority‑based evaluation of each requirement based on experience implement‑ ing and testing security requirements. The main focus is on comparing risk reduction with the effort to implement the requirement. Another key factor is to keep a low barrier to entry.</blockquote><p></p><p>I've provided a copy of the standard below and if you're not familiar with ASVS, I'd really recommend checking it out, even if only for you to use it as a reference rather than a hard requirement. </p><div class=\"kg-card kg-file-card\"><a class=\"kg-file-card-container\" href=\"https://scotthelme.ghost.io/content/files/2025/06/OWASP_Application_Security_Verification_Standard_5.0.0_en.pdf\" title=\"Download\" download><div class=\"kg-file-card-contents\"><div class=\"kg-file-card-title\">OWASP_Application_Security_Verification_Standard_5.0.0_en</div><div class=\"kg-file-card-caption\"></div><div class=\"kg-file-card-metadata\"><div class=\"kg-file-card-filename\">OWASP_Application_Security_Verification_Standard_5.0.0_en.pdf</div><div class=\"kg-file-card-filesize\">504 KB</div></div></div><div class=\"kg-file-card-icon\"><svg viewbox=\"0 0 24 24\"><defs><style>.a{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px;}</style></defs><title>download-circle</title><polyline class=\"a\" points=\"8.25 14.25 12 18 15.75 14.25\"/><line class=\"a\" x1=\"12\" y1=\"6.75\" x2=\"12\" y2=\"18\"/><circle class=\"a\" cx=\"12\" cy=\"12\" r=\"11.25\"/></svg></div></a></div><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/Report-URI-Twitter-Card.png\" class=\"kg-image\" alt=\"OWASP ASVS 5.0.0 is here!\" loading=\"lazy\" width=\"800\" height=\"400\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/06/Report-URI-Twitter-Card.png 600w, https://scotthelme.ghost.io/content/images/2025/06/Report-URI-Twitter-Card.png 800w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><h4 id=\"the-parts-where-we-can-help\">The parts where we can help</h4><p>As a platform focused on providing organisations with the tools they need to be secure, there is of course a huge overlap between the requirements in the OWASP ASVS 5.0.0 standard, and <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a>'s wide range of products and features. Some of these requirements are more generalised and are something we can help with on a more holistic approach, and some of them are slam-dunk cases where Report URI can 100% help you meet that requirement. We'll go through these requirements at a high level and look at how Report URI can help, and we'll also provide additional guidance and context to help you understand the requirements better. Let's start with the big hitters first, and go from there, and also remember to check the compliance level which we will provide for each requirement to see if it's something you will need to consider. Here's a quick summary of the compliance levels if you aren't familiar with them.</p><p>✅ <strong>Level 1 — Informational / Low Assurance</strong><br>Low-risk apps (e.g. simple brochure sites or MVPs).</p><p><strong>✅✅ Level 2 — Standard Assurance (Baseline Compliance)</strong><br>Apps handling personal data, business logic, or moderate sensitivity.</p><p><strong>✅✅✅ Level 3 — High Assurance</strong><br>Financial systems, healthcare apps, and other high-value or high-impact systems.</p><p></p><p>You can assess which level of the requirements is suitable for your application, and then you can use that to guide you through the following sections to determine which apply to you and your application.</p><p></p><h4 id=\"v31-web-frontend-security-documentation\">V3.1 Web Frontend Security Documentation</h4><p>This is a pretty sensible requirement to protect against the impact of malicious content find its way in to your site, and as a Level 1 requirement, is applicable to all sites.</p><p></p><p><strong>3.2.1 (Level 1)</strong></p><blockquote>Verify that security controls are in place to prevent browsers from rendering content or functionality in HTTP responses in an incorrect context (e.g., when an API, a user‑uploaded file or other resource is requested directly). Possible controls could include: not serving the content unless HTTP request header fields (such as Sec‑Fetch‑*) indicate it is the correct context, using the sandbox directive of the Content‑Security‑Policy header field or using the attachment disposition type in the Content‑Disposition header field.\n</blockquote><p></p><p>Whenever you're using Content Security Policy, it's <em>always </em>advisable to monitor that policy and ensure there isn't anything happening that you weren't expecting. The sandbox directive is particularly powerful in the suggested context and allows you to isolate the downloaded content.</p><p></p><h4 id=\"v34-browser-security-mechanism-headers\">V3.4 Browser Security Mechanism Headers\n</h4><p>Honestly at this point in the realm of security online, who doesn't love Security Headers?! There is so much powerful protection you can leverage by configuring these relatively simple mechanisms, it's no wonder that they got an entire section in the standard. You should read this entire section, absolutely, but here are the parts we can help with specifically.</p><p></p><p><strong>3.4.3 (Level 2 and Level 3)</strong></p><blockquote>Verify that HTTP responses include a Content‑Security‑Policy response header field which defines directives to ensure the browser only loads and executes trusted content or resources, in order to limit execution of malicious JavaScript. As a minimum, a global policy must be used which includes the directives object‑src ‘none’ and base‑uri ‘none’ and defines either an allowlist or uses nonces or hashes. For an L3 application, a per‑response policy with nonces or hashes must be defined.</blockquote><p></p><p><strong>3.4.6 (Level 2)</strong></p><blockquote>Verify that the web application uses the frame‑ancestors directive of the Content‑Security‑Policy header field for every HTTP response to ensure that it cannot be embedded by default and that embedding of specific resources is allowed only when necessary. Note that the X‑Frame‑Options header field, although supported by browsers, is obsolete and may not be relied upon.</blockquote><p></p><p><strong>3.4.7 (Level 3)</strong></p><blockquote>Verify that the Content‑Security‑Policy header field specifies a location to report violations.</blockquote><p></p><p>That last one is particularly noteworthy, and I'd say that any application with a CSP in place should be monitoring for those violations because you want to know if things have been configured incorrectly, or worse, an attack is currently ongoing! All of that guidance is sensible though and these are all standard things that we'd be recommending to our customers anyway.</p><p></p><p><strong>V3.6 External Resource Integrity</strong></p><p>Another common risk being addressed by this requirement is that of your JavaScript supply chain. Over the years we have seen so many attacks where a website is compromised not because they were breached, but because one of their dependencies was breached instead! Whilst this requirement focuses specifically on the venerable Subresource Integrity (SRI), at the time of writing we have a new feature in beta that will go far beyond the capabilities of SRI and provide even more value. Reach out if you're interested in joining the beta!</p><p></p><p><strong>3.6.1 (Level 3)</strong></p><blockquote>Verify that client‑side assets, such as JavaScript libraries, CSS, or web fonts, are only hosted externally (e.g., on a Content Delivery Network) if the resource is static and versioned and Subresource Integrity (SRI) is used to validate the integrity of the asset. If this is not possible, there should be a documented security decision to justify this for each resource.</blockquote><p></p><h4 id=\"getting-started\">Getting Started</h4><p>If you want to start the process of improving the security of your web application, you can get started with a free trial on our website right now, or reach out to our team if you'd like some support through this process. Maybe you're looking to avoid the risk of a costly data breach in the future, maybe you have some particular compliance requirement you're trying to meet, or perhaps you've identified a specific concern you'd like to address. Either way, we're here to help, and we have more than a decade of experience to draw from as we support you.</p><p>To achieve this, the ASVS standard is a great basis to work from, and I want to quote the following objectives from the standard itself:</p><p></p><blockquote>•<strong> Use as a metric</strong> - Provide application developers and application owners with a yardstick with which to assess the degree of trust that can be placed in their Web applications,<br>•<strong> Use as guidance</strong> - Provide guidance to security control developers as to what to build into security controls in order to satisfy application security requirements, and<br>•<strong> Use during procurement</strong> - Provide a basis for specifying application security verification requirements in contracts.</blockquote><p></p><p>No matter which angle you're coming from, I'm hoping to see increased adoption of the OWASP ASVS requirements over time and the benefits that will bring to security across the Web.</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg",
"image": "https://scotthelme.ghost.io/content/images/2025/07/OWASP_ASVS_Linkedin_Banner-01.jpg",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "OWASP",
"term": "OWASP",
"url": null
},
{
"label": "ASVS",
"term": "ASVS",
"url": null
},
{
"label": "Report URI",
"term": "Report URI",
"url": null
}
]
},
{
"id": "6863a159db89a500016be6a1",
"title": "Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀",
"description": "<p>We’ve just passed a monumental milestone: <strong>2 trillion events processed</strong> through <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a>!!! That’s <em>2,000,000,000,000</em> events for <a href=\"https://report-uri.com/products/content_security_policy?ref=scotthelme.ghost.io\" rel=\"noreferrer\">CSP</a>, <a href=\"https://report-uri.com/products/network_error_logging?ref=scotthelme.ghost.io\" rel=\"noreferrer\">NEL</a>, <a href=\"https://report-uri.com/products/dmarc_monitoring?ref=scotthelme.ghost.io\" rel=\"noreferrer\">DMARC</a>, and other browser-generated and email telemetry reports—ingested, parsed, and processed for our customers!</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image.png\" class=\"kg-image\" alt loading=\"lazy\" width=\"565\" height=\"215\"></a></figure><p></p><p>This is a phenomenal milestone to achieve</p>",
"url": "https://scotthelme.ghost.io/trillion-with-a-t-surpassing-2-trillion-events-processed/",
"published": "2025-07-02T18:12:32.000Z",
"updated": "2025-07-02T18:12:32.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\"><p>We’ve just passed a monumental milestone: <strong>2 trillion events processed</strong> through <a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI</a>!!! That’s <em>2,000,000,000,000</em> events for <a href=\"https://report-uri.com/products/content_security_policy?ref=scotthelme.ghost.io\" rel=\"noreferrer\">CSP</a>, <a href=\"https://report-uri.com/products/network_error_logging?ref=scotthelme.ghost.io\" rel=\"noreferrer\">NEL</a>, <a href=\"https://report-uri.com/products/dmarc_monitoring?ref=scotthelme.ghost.io\" rel=\"noreferrer\">DMARC</a>, and other browser-generated and email telemetry reports—ingested, parsed, and processed for our customers!</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"565\" height=\"215\"></a></figure><p></p><p>This is a phenomenal milestone to achieve in the year that will mark our 10th Birthday, and shows just how quickly we're still growing!</p><p><strong>May 2015</strong>: 0 Events - Launch Day [<a href=\"https://scotthelme.co.uk/csp-and-hpkp-violation-reporting-with-report-uri-io/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>]<br><strong>March 2021</strong>: 500,000,000,000 Events - 500 Billion Events [<a href=\"https://www.facebook.com/reporturi/posts/pfbid0jZJQRbPsDZZsKnA21QzwQa7CcATd1AjD8Zwr4EPHeqytrDAC5hTjuSc8wbH5xuz2l\" rel=\"noreferrer\">source</a>]<br><strong>February 2022</strong>: 1,000,000,000,000 Events - 1 Trillion Events [<a href=\"https://x.com/Scott_Helme/status/1490066996268052494?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>]<br><strong>November 2023</strong>: 1,500,000,000,000 Events - 1.5 Trillion Events [<a href=\"https://scotthelme.co.uk/report-uri-a-week-in-numbers-2023-edition/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">source</a>]<br><strong>July 2025</strong>: 2,000,000,000,000 Events - 2 Trillion Events [keep reading!]</p><p></p>\n<!--kg-card-begin: html-->\n<a href=\"https://report-uri.com/?ref=scotthelme.ghost.io\" style=\"box-shadow: none;\">\n <img src=\"https://scotthelme.co.uk/content/images/2025/07/report-uri---Copy.png\" style=\"max-width: 75%;\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\">\n</a>\n<!--kg-card-end: html-->\n<p></p><p>To mark this incredible occasion, I'm excited to announce the <a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Report URI Global Telemetry Dashboard</a>. I often get questions about how many telemetry events we process per day, how many browsers send us telemetry, where does the telemetry come from?! Well, now, you can see the answer to all of those questions and a lot more. If you like cool graphs, geeky data, and insights into things operating at enormous scale, you're really going to enjoy this! 😎</p><p></p><h4 id=\"the-report-uri-global-telemetry-dashboard\">The Report URI Global Telemetry Dashboard</h4><p>Taking screenshots and sharing them here really isn't going to do this justice, and there is a special surprise on one of the dashboards that I'm not going to spoil for you here! If you want to see all of this stuff live and have a play around with it yourself, head over to the <a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">dashboard</a> now. For those of you still here, I want to share some information and insights into the data. </p><p>As of the time of writing, this is the exact data showing on the homepage of the dashboard:</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-1.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"782\" height=\"717\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-1.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-1.png 782w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><p>That incredible number of almost 2 trillion events processed is right there, and a pretty incredible average of over <em>12,000 events per second</em> that we processed yesterday! The thing that always strikes me about this number is that this isn't requests that we're serving a response to, nothing here can be served from cache. Each inbound event needs processing, analysing, and storing, with the potential for alerts to be raised, Threat Intelligence to be generated, customers to be notified and much, much more. Another impressive statistic is just how many unique clients out there are sending us telemetry data, reaching almost a quarter of a billion unique clients in just the last 30 days! That's a huge amount of insight into the Web ecosystem and a significant contributing factor to the benefit we can bring by leveraging that insight to spot attacks faster. The remainder of the <a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Dashboard Home</a> page shows additional insights into our traffic volumes, showing the hourly data for the last 24 hours, and the daily data for the last 7 and 30 days.</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-5.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"950\" height=\"562\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-5.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-5.png 950w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-3.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"940\" height=\"555\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-3.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-3.png 940w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/home/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-4.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"943\" height=\"550\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-4.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-4.png 943w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><p>You can already start to see some interesting trends in that data, and these are trends that we've been tracking for a really long time. The 24 hour graph clearly shows the busy periods within a typical day for us, and it's not surprising to guess at what time of the day we see the most telemetry! Both the 7 day and 30 day graphs show our typical weekly trends too, with far more activity showing Mon-Fri than at the weekend. </p><p></p><h4 id=\"the-global-heat-map\">The Global Heat Map</h4><p>This particular page in the dashboard is my second-favourite, and it's easy to see why. Looking at the telemetry we received over the last 30 days, this <a href=\"https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">heat map</a> shows where that telemetry came from!</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-6.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"1475\" height=\"740\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-6.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-6.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-6.png 1475w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><p>You can zoom in to the map and really start to see where the busiest locations for us receiving telemetry are, with some expected locations showing up as the hottest areas of the map, and some more surprising areas showing up in there too! Zooming all the way in to the datacentre just down the road from me in Manchester, you can see exact counts of telemetry that went through that location in the last 30 days.</p><p></p><figure class=\"kg-card kg-image-card\"><a href=\"https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-7.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"748\" height=\"825\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-7.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-7.png 748w\" sizes=\"(min-width: 720px) 720px\"></a></figure><p></p><p>Have a fly around the map and see what your nearest location is and what volume of telemetry went through that location in the last 30 days on our <a href=\"https://dash.report-uri.com/heatmap/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Heat Map</a>!</p><p></p><h4 id=\"we-have-a-pew-pew-map\">We have a Pew Pew Map!</h4><p>Okay okay I said I wasn't going to spoil the surprise, but I am way too excited about this one! Ever since seeing the original Norse Attack Map over 10 years ago, I've always wanted my own <a href=\"https://dash.report-uri.com/pewpewmap/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Pew Pew Map</a>, and now we have one! The GIF here is simply not going to do this justice, so you absolutely should head over to the page and see this thing live, in action, right now!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/pew-pew-map-1.gif\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"1602\" height=\"1015\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/pew-pew-map-1.gif 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/pew-pew-map-1.gif 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/07/pew-pew-map-1.gif 1600w, https://scotthelme.ghost.io/content/images/2025/07/pew-pew-map-1.gif 1602w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>What you're seeing here is <em>actual telemetry</em> being to sent to us! The map is 5 minutes behind real-time, and the data is real. The more red the zap is, the more telemetry we received from that location, and the faster the zap is moving, the more frequently we are receiving telemetry from that location! There are no estimations here, there is no mock data. This is a direct representation of real data, and I think that just makes this even more awesome! Our primary datacentre is located in SFO, as I'm sure you've guessed, and we do have a secondary location in AMS for our more regulated EU customers, but they are excluded from this data. </p><p>I'm tempted to get myself a second monitor off to the side of my desk just for this map, it really is quite cool to watch. If you come back at different times during the day, you can clearly see the different trends in our traffic patterns for inbound telemetry, to the point where you can almost track the Sun moving across the map based on the telemetry volumes!</p><p></p><h4 id=\"country-specific-data\">Country Specific Data</h4><p>There's just no way you can top a Pew Pew Map, but we do still have a heap of interesting data that we can peruse. The next page is our <a href=\"https://dash.report-uri.com/country/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Country Specific Data</a>, which in many ways shows exactly what you might expect, with a couple of surprises in there too.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-8.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"850\" height=\"493\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-8.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-8.png 850w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>The US is by far our largest source of telemetry data, which makes a lot of sense as a large portion of our customers are US-based companies, likely with US based visitors themselves. People may be surprised to see Japan so high up that list, but we do have a few quite notable customers in Japan, including a rather large coffee company, so they generate more than their fair share of telemetry. After that, the UK is coming in, followed by India that are helped by their population size, and then a broad selection of countries that you might expect to see up there too. The table below that gives exact details on our Top 20 Countries, but here are the top 3 for you.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-9.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"754\" height=\"305\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-9.png 600w, https://scotthelme.ghost.io/content/images/2025/07/image-9.png 754w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><h4 id=\"some-technical-data\">Some technical data!</h4><p>To close things out for now, we have our <a href=\"https://dash.report-uri.com/info/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Detailed Information</a> dashboard that goes into a little bit more of the technical data behind our inbound telemetry. We do have plans to expand this to incorporate a lot more metrics, but for now, I think we have a pretty good selection available. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-10.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"1027\" height=\"1153\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-10.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-10.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-10.png 1027w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Looking at the HTTP Version and TLS Protocol used, you can see that we clearly have a very wide selection of modern clients out there, using TLSv1.3 almost exclusively, and HTTP/2 and HTTP/3 taking the lion's share of the application protocol. When responding to inbound telemetry requests, our most common response is an empty txt response to save as much bandwidth and overhead as possible, we simply reply with an empty 201 to acknowledge receipt. After the 201, we have a variety of response codes that we might use along with a message in the body to give more information on the problem to help with debugging. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/07/image-11.png\" class=\"kg-image\" alt=\"Trillion with a T: Surpassing 2 Trillion Events Processed!🚀🚀\" loading=\"lazy\" width=\"1025\" height=\"1145\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/07/image-11.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/07/image-11.png 1000w, https://scotthelme.ghost.io/content/images/2025/07/image-11.png 1025w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Taking a look across the next set of data, I'm often quite surprised to see Firefox so heavily represented in the Client UA, but there are a wider selection of Chromium based browsers in the mix. The front runners for Top Colo By Requests won't be a huge surprise given the country data above, but it is nice to see a breakdown of where our most popular locations are. Closing it out, we have a little more client data showing the Client OS, with a probably expected Windows and iOS being the most dominant, and Client Type showing that Desktop is hanging on to the top spot with Mobile close behind!</p><p></p><h4 id=\"theres-more-to-come\">There's more to come!</h4><p>I created this dashboard to scratch an itch, I got to play with some cool data, and as I mentioned above, I've <em>always</em> wanted a pew pew map! I also really enjoyed stepping away from the more serious responsibilities of being 'CEO and Founder' to play around with cool technology and build something awesome! That is, after all, why I founded the company in the first place. </p><p>I hope the dashboard will be interesting and provide some insight into what we're doing, and if there's something you'd like to see added to the dashboard, or any questions you have, just let me know in the comments below. As I come across any interesting metrics in the future, I'll be sure to add them to the dashboard, and I'd love to hear your suggestions too!</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png",
"image": "https://scotthelme.ghost.io/content/images/2025/07/Screenshot-2025-07-01-113513.png",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Report URI",
"term": "Report URI",
"url": null
},
{
"label": "report-uri.com",
"term": "report-uri.com",
"url": null
}
]
},
{
"id": "6849e889cca6290001a6e931",
"title": "V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!",
"description": "<p>In my <a href=\"https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">first blog post about hacking my Tesla Powerwalls</a>, I laid out all of the foundations and information about my home energy setup. You really need to read that blog post first as I'm going to be building on all of that work here, and assuming that</p>",
"url": "https://scotthelme.ghost.io/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/",
"published": "2025-06-13T10:52:02.000Z",
"updated": "2025-06-13T10:52:02.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\"><p>In my <a href=\"https://scotthelme.co.uk/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">first blog post about hacking my Tesla Powerwalls</a>, I laid out all of the foundations and information about my home energy setup. You really need to read that blog post first as I'm going to be building on all of that work here, and assuming that you're familiar with everything in that post.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/image.png\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"432\" height=\"132\"></figure><p></p><h4 id=\"the-final-piece-of-the-puzzle\">The final piece of the puzzle</h4><p>In my previous post, I had focused on solving a particular problem. My Powerwalls would charge up overnight on the very cheap off-peak rate so that they could run my house during the day, avoiding the expensive peak rate. The problem I was coming across was that the batteries would hit 100% SoC overnight, start depleting during the day, but when the sun started shining, our solar array could fill the batteries back up to 100% SoC and then export excess solar production at an almost worthless cost. I created an automation to stop the batteries from charging once they hit 75% SoC, which left plenty of room for excess solar production to fill up the batteries during the day rather than exporting the energy. This was an improvement, but it wasn't an ideal solution, so I set out to resolve it. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/OE-Logo-RGB_Unstacked-white-xl-octopus--1--min-1.jpg\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"350\" height=\"207\"></figure><p></p><h4 id=\"my-new-export-tariff\">My new export tariff</h4><p>I've finally managed to get myself on a decent export tariff, meaning that I'm now being paid much more for energy that I export to the grid. I feel that energy I export to the grid should be paid for at the same rate as energy I import from the grid, but that's unfortunately not the case. I am, however, now being paid almost <em>4x more</em> than I was before for energy export, and it still falls 50% short of what my import costs! My import tariff is still the same, and my export tariff is now much better.</p><p></p><table>\n<thead>\n<tr>\n<th>Activity</th>\n<th>Time</th>\n<th>Cost</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Import</td>\n<td>05:30 to 23:30</td>\n<td>£0.28/kWh</td>\n</tr>\n<tr>\n<td>Import</td>\n<td>23:30 to 05:30</td>\n<td>£0.07/kWh</td>\n</tr>\n<tr>\n<td>Export</td>\n<td>00:00 to 23:59</td>\n<td><s>£0.04/kWh</s> <em><strong>£0.15/kWh</strong></em></td>\n</tr>\n</tbody>\n</table>\n<p></p><p>Of course, the fact that I can purchase electricity for £0.07/kWh overnight and then export it for £0.15/kWh is quite an attractive proposition, and something that I'd like to take advantage of. This is something known as load-shifting, or <a href=\"https://en.wikipedia.org/wiki/Load_management?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Load Management</a>, where suppliers want to encourage people to move their usage into more desirable time periods, and is the reason for the very existence of the cheap overnight tariffs in the first place. I already have the batteries and solar array to allow me to do this, so all I needed was a solution to automate it.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/image-4.png\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"521\" height=\"259\"></figure><p></p><h4 id=\"using-home-assistant-and-teslemetry-again\">Using Home Assistant and Teslemetry, again!</h4><p>In my previous post, I used <a href=\"https://teslemetry.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Teslemetry</a> to control when the batteries could charge from the grid to introduce the ceiling of 75% SoC to leave room for excess solar production during the day. Now, though, I want to go one step further and charge the batteries to 100% SoC overnight, and slowly let them feed back to the grid during the day if we have excess capacity, along with excess solar production, to leave me with enough power to get back to the cheap rate at 23:30 again.</p><p></p><p>The <a href=\"https://www.home-assistant.io/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Home Assistant</a> automation to do this was surprisingly simple, so I will post the whole thing here, and then we can talk through what it does.</p><p></p><pre><code class=\"language-yaml\">alias: Dynamic Battery Export Control\ndescription: >-\n Adjust export setting and export mode display based on battery SoC and\n time-of-day\ntriggers:\n - minutes: /15\n trigger: time_pattern\nactions:\n - variables:\n now_ts: \"{{ now().timestamp() }}\"\n today: \"{{ now().date().isoformat() }}\"\n start_ts: \"{{ (today ~ 'T05:30:00') | as_datetime | as_timestamp }}\"\n end_ts: \"{{ (today ~ 'T23:30:00') | as_datetime | as_timestamp }}\"\n end_for_target_ts: \"{{ (today ~ 'T23:15:00') | as_datetime | as_timestamp }}\"\n current_soc: \"{{ states('sensor.shireburn_charge') | float(0) }}\"\n - choose:\n - conditions:\n - condition: template\n value_template: \"{{ now_ts < start_ts or now_ts > end_ts }}\"\n sequence:\n - target:\n entity_id: input_number.target_soc\n data:\n value: 100\n action: input_number.set_value\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: No Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'never' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: never\n action: select.select_option\n default:\n - variables:\n total_secs: \"{{ end_for_target_ts - start_ts }}\"\n elapsed_secs: \"{{ now_ts - start_ts }}\"\n progress: >-\n {{ (elapsed_secs / total_secs) if elapsed_secs < total_secs else 1.0\n }}\n target_soc: \"{{ 95 - (85 * (progress ** 1.5)) }}\"\n - target:\n entity_id: input_number.target_soc\n data:\n value: \"{{ target_soc | round(0) }}\"\n action: input_number.set_value\n - choose:\n - conditions:\n - condition: template\n value_template: |\n {% set hour = now().hour %} {% if hour < 23 %}\n {{ current_soc > (target_soc + 5) }}\n {% else %}\n {{ current_soc > target_soc }}\n {% endif %}\n sequence:\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: Battery + Solar Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'battery_ok' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: battery_ok\n action: select.select_option\n - conditions:\n - condition: template\n value_template: \"{{ current_soc < (target_soc - 5) }}\"\n sequence:\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: No Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'never' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: never\n action: select.select_option\n default:\n - target:\n entity_id: input_text.export_mode_state\n data:\n value: Solar Export\n action: input_text.set_value\n - condition: template\n value_template: \"{{ states('select.shireburn_allow_export') != 'pv_only' }}\"\n - target:\n entity_id: select.shireburn_allow_export\n data:\n option: pv_only\n action: select.select_option\nmode: single\n</code></pre><p></p><p></p><p>The automation runs every 15 minutes and first checks if we're in the off-peak window of 23:30 to 05:30, and if we are, it sets the Target SoC to 100% and that's it, we want the battery charging. If we're in the peak window of 05:30 to 23:30, then a few things happen.</p><p>The first thing it does is calculate how far through the peak window we are, which is used to calculate the Target SoC. Originally, I had a linear decline throughout the day which seemed like it should work just fine. If we were 25% of the way through the window, the Target SoC would be 75%, at 50% of the way through the window the Target SoC was 50%, and 75% of the way through the window, the Target SoC would be 25%.</p><p>Plotting that out it looks like the graph below, and it did work fine, but we had some evenings where our usage would spike quite high and there wasn't enough of a buffer left in the battery, leaving us short on battery power. I didn't want to have to worry about problems like this, and whilst it worked fine for the most part, it needed improving.</p><p></p><figure class=\"kg-card kg-image-card kg-card-hascaption\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/Screenshot-2025-06-11-213247.png\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"478\" height=\"340\"><figcaption><span style=\"white-space: pre-wrap;\">Target SoC with a linear decline</span></figcaption></figure><p></p><p>I changed the Target SoC to an exponential decline throughout the day, meaning it would hold a slightly higher SoC during the day, but then tail off quite hard at the end of the day, making the Target SoC curve look like this. </p><figure class=\"kg-card kg-image-card kg-card-hascaption\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/Screenshot-2025-06-11-213303.png\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"480\" height=\"340\"><figcaption><span style=\"white-space: pre-wrap;\">Target SoC with an exponential decline</span></figcaption></figure><p></p><p>This gave us much more breathing room if we had a sudden increase in usage during the evening, and if not, the batteries could just export at a more aggressive rate to reach the lower Target SoC. This worked much better for us, gave us the breathing room we needed, but still meant that we could export any spare capacity during the peak window.</p><p>Now that the Target SoC is established, we can decide on the export mode to keep the Actual SoC aligned with the Target SoC. For that, there are the following 3 options.</p><p></p><p><strong>Condition 1:</strong> Battery SoC > (Target SoC + 5) = Export Battery + Solar<br><strong>Condition 2:</strong> Battery SoC < (Target SoC - 5) = Export Nothing<br><strong>Default:</strong> Export Solar Only<br><br></p><p>Condition 1: If we're more than 5% above target, we should export from the solar array and the batteries as we have spare capacity in the batteries. This will bring the Actual SoC down to the Target SoC faster as time goes by.</p><p>Condition 2: If we're more than 5% below target we should export nothing, as we're trying to preserve the battery capacity by using solar to power the house as much as possible. This reduces the load on the battery and allows the Target SoC to fall over time while holding the Actual SoC as high as possible.</p><p>Default: The aim here is to run the house on the batteries and export solar production to the grid, trying to maintain a steady decline in Actual SoC throughout the day that should align it with Target SoC.</p><p>If you look at the graphs above for Actual SoC, you can see the step changes every 15 minutes when the automation runs and changes the export mode to adjust the Actual SoC as needed, keeping things well aligned! One thing I am keeping in mind is that we're in summer right now, so our solar production is quite good and our energy usage is probably on the lower end of what's typical. As we approach autumn and then winter, I'm wondering if I might need to make the Target SoC curve a little steeper, to hold a higher charge during the day and tail off more aggressively in the evening, to give us a little more breathing room. Only time will tell on that one, but if there's a change required, I will come back and update this post with a note.</p><p></p><h4 id=\"seeing-it-in-action\">Seeing it in action</h4><p>Now that I've run this automation for over a week, it's had a chance to run through days where we have exceptionally low solar production (British Summer), and we export very little, to days like yesterday where we have amazing solar production all day (one day in British Summer), and as a result, we see some great export rates too!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/IMG_6398-1.jpeg\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"600\" height=\"979\" srcset=\"https://scotthelme.ghost.io/content/images/2025/06/IMG_6398-1.jpeg 600w\"></figure><p></p><p>The top graph shows our export for the day and you can clearly see the Solar Bell (the solar production bell curve) from sunrise to sunset. Dotted throughout the day you can also see the occasional spikes in our export, where the automation has detected excess capacity in the batteries and released it into the grid, causing a spike in our 30 minute export window. You can then clearly see the exponential decline for battery capacity kicking in as we release excess battery capacity late into the evening. The bottom graph then shows our import, which looks very different, hauling in that cheap power overnight and not importing any energy during the day. If you want to see those graphs as kWh instead of cost, here they are.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/IMG_6399-1.jpg\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"600\" height=\"966\" srcset=\"https://scotthelme.ghost.io/content/images/2025/06/IMG_6399-1.jpg 600w\"></figure><p></p><h4 id=\"maximum-import-maximum-export\">Maximum import, maximum export</h4><p>Once we hit the cheap tariff at 23:30, the heavy import begins. Not only do our Powerwalls begin charging, capable of pulling in a total of 15kW, we have quite a few other systems that make use of the off-peak rate too. If either one of our two EVs are plugged in, they will begin charging at 7kW, the hot tub only heats and filters the water overnight, so that fires up using almost 4kW, and a variety of smaller appliance like the tumble dryer might be waiting too, using a few more kW of power.</p><p>This means that we can quite easily hit our maximum import limit of 24kW, at which point the Powerwalls will automatically throttle their import to prevent us from exceeding the limit and blowing our 100A main fuse (100A x 240v = 24,000W). Given that we have a 24kW import limit, you'd think that we have a 24kW export limit too, but, we don't... For reasons known only to them, our Distribution Network Operator determined that we are only able to export at a maximum rate of 3.869kW, which you can see on the negative portion of the graph during the day where we hit the flat bottom of our maximum export rate. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/IMG_6400.jpg\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1290\" height=\"764\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/06/IMG_6400.jpg 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/IMG_6400.jpg 1000w, https://scotthelme.ghost.io/content/images/2025/06/IMG_6400.jpg 1290w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Side note: Yes, I know our nominal voltage on the grid in the UK is 230v after we harmonised with Europe, but in reality, it's often much higher. My 100A fuse is also BS1361 rated, meaning it can realistically carry 120A indefinitely, and last for prolonged periods at 125A. Even at our minimum tolerance on grid voltage of 207v, we'd still only be pulling 116A at 24kW (24,000w / 207v = 116A). Here's the grid voltage for the same time window shown in the above graph to show what I mean.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/06/IMG_6401.jpg\" class=\"kg-image\" alt=\"V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1290\" height=\"734\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/06/IMG_6401.jpg 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/06/IMG_6401.jpg 1000w, https://scotthelme.ghost.io/content/images/2025/06/IMG_6401.jpg 1290w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>It is interesting to see what effect solar PV has on the peak voltage, getting us quite high at 255v, and then at the opposite end of the scale under heavy load, we can dip all the way down to 233v!</p><p></p><h4 id=\"can-we-control-the-powerwall-charge-rate\">Can we control the Powerwall charge rate?</h4><p>Just before we wrap up, there is one final thing that I haven't yet found a solution for that I would like to resolve, so if anyone has any ideas, please let me know in the comments. When the Powerwalls start charging from the grid, they charge at their absolute maximum possible rate of 5kW per battery, giving us our total import rate of 15kW into our 3 batteries. Each battery has a usable capacity of 13.5kWh, so, ignoring efficiency for easy maths, at maximum charging rate they can go from 0% to 100% in 2 hours and 45 minutes.</p><p>It seems unnecessary to charge at their absolute maximum rate which will have them sat at 100% SoC for more than half of our off-peak window, and have charged them at full power when it wasn't needed. I'd much prefer to be able to set them to charge at 2.5kW per battery, which will still allow them to go from 0% to 100% during our off-peak window. Charging them more slowly is better for the batteries in the long run due to the lower charge rate, and, they will spend less time at 100% SoC which is also better for their health. If I had dynamic control of the charge rate I could even calculate what rate was required for them to hit 100% SoC right as the off-peak window ends! </p><p>At present, I can't see any way to do this, either setting a static import limit per battery or a dynamic limit based on SoC and time remaining. I know I can restrict the overall site import limit with the Tesla One app, but that's not useful here as it isn't flexible and could result in no import into the batteries if something else is using the import! I'm a little stuck on this one, do you have any ideas?</p><p></p><p></p>\n<!--kg-card-begin: html-->\n<style>\n pre[class*=\"language-\"],\n code[class*=\"language-\"] {\n font-size: 0.85em !important;\n}\n</style>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css\" integrity=\"sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\">\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js\" integrity=\"sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js\" integrity=\"sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<!--kg-card-end: html-->",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/06/tesla-powerwalls-export.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Home Assistant",
"term": "Home Assistant",
"url": null
},
{
"label": "Teslemetry",
"term": "Teslemetry",
"url": null
},
{
"label": "Tesla Powerwall",
"term": "Tesla Powerwall",
"url": null
}
]
},
{
"id": "6807855b51addd000148f995",
"title": "Shorter certificates are coming!",
"description": "<p>Well, I was certainly hoping for this result, but wasn't necessarily expecting it! I'm pleased to report that Ballot SC-081v3 passed, and that shorter certificate lifetimes are now coming!</p><p></p><h4 id=\"the-schedule\">The Schedule</h4><p>I will go into more detail later in the post, but right now, let'</p>",
"url": "https://scotthelme.ghost.io/shorter-certificates-are-coming/",
"published": "2025-04-22T13:37:29.000Z",
"updated": "2025-04-22T13:37:29.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp\" alt=\"Shorter certificates are coming!\"><p>Well, I was certainly hoping for this result, but wasn't necessarily expecting it! I'm pleased to report that Ballot SC-081v3 passed, and that shorter certificate lifetimes are now coming!</p><p></p><h4 id=\"the-schedule\">The Schedule</h4><p>I will go into more detail later in the post, but right now, let's cover the important details! Here is the schedule that was proposed and voted on, and will now come into effect:</p><p></p><table>\n<thead>\n<tr>\n<th><strong>Certificate issued on or after</strong></th>\n<th><strong>Certificate issued before</strong></th>\n<th><strong>Maximum Validity Period</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td></td>\n<td>March 15, 2026</td>\n<td>398 days</td>\n</tr>\n<tr>\n<td>March 15, 2026</td>\n<td>March 15, 2027</td>\n<td>200 days</td>\n</tr>\n<tr>\n<td>March 15, 2027</td>\n<td>March 15, 2029</td>\n<td>100 days</td>\n</tr>\n<tr>\n<td>March 15, 2029</td>\n<td></td>\n<td>47 days</td>\n</tr>\n</tbody>\n</table>\n<p></p><p>This means that the important dates are as follows:</p><p><strong>March 15th 2026</strong>: <em>All new certificates capped at 200 days validity</em></p><p><strong>March 15th 2027</strong>: <em>All new certificates capped at 100 days validity</em></p><p><strong>March 15th 2029</strong>: <em>All new certificates capped at 47 days validity!</em></p><p></p><p>It's a little sad that the dates slipped since the initial proposal of this ballot, hence the 'v3', but still, we now have a definitive date in the future when we know these things will be happening.</p><p>If we look at the progress of shortening certificates over time, we can see that the trend fits well, and if anything, our progress had stalled out a little, but we're now back on track, even if at a reduced pace.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/validity-period-final-data.png\" class=\"kg-image\" alt=\"Shorter certificates are coming!\" loading=\"lazy\" width=\"1227\" height=\"715\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/validity-period-final-data.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/validity-period-final-data.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/validity-period-final-data.png 1227w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>You can see my views on the progress of this ballot in my earlier post, <a href=\"https://scotthelme.co.uk/are-shorter-certificates-finally-coming/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Are shorter certificates finally coming?!</a>.</p><p></p><h4 id=\"who-voted-for-what\">Who voted for what?</h4><p>As the CA/B Forum votes are public, we can take a look at who voted for what, and in the past, that has given us some interesting insight into the sentiment across industry. If you want to view the official announcement, you can do that <a href=\"https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/9768xgUUfhQ?ref=scotthelme.ghost.io\" rel=\"noreferrer\">here</a>, but here are the results.</p><p></p><blockquote>The voting period for <strong>SC-081v3: Introduce Schedule of Reducing Validity and Data Reuse Periods</strong> has completed. The ballot has: <strong>PASSED<br><br>Voting Results<br><br>Certificate Issuers</strong><br><br>30 votes in total:<br> * 25 voting YES: Amazon, Asseco Data Systems SA (Certum), Buypass AS, Certigna (DHIMYOTIS), Certinomis, DigiCert, Disig, D-TRUST, eMudhra, Fastly, GlobalSign, GoDaddy, HARICA, iTrusChina, Izenpe, NAVER Cloud Trust Services, OISTE Foundation, Sectigo, SHECA, SSL.com, SwissSign, Telia Company, TrustAsia, VikingCloud, Visa<br> * 0 voting NO:<br> * 5 ABSTAIN: Entrust, IdenTrust, Japan Registry Services, SECOM Trust Systems, TWCA<br><br><strong>Certificate Consumers</strong><br><br>4 votes in total:<br> * 4 voting YES: Apple, Google, Microsoft, Mozilla<br> * 0 voting NO:<br> * 0 ABSTAIN:</blockquote><p></p><p>That is absolutely phenomenal support, and I think largely due to the open nature of the proposal, seeking to gather industry feedback and adapting the proposal based on that feedback. It's a little disappointing to see CAs abstain from voting, especially IdenTrust, but here are the responses from those CAs who abstained and provided a <a href=\"https://groups.google.com/a/groups.cabforum.org/g/servercert-wg/c/bvWh5RN6tYI?ref=scotthelme.ghost.io\" rel=\"noreferrer\">reason</a>:</p><p></p><blockquote>IdenTrust abstains from voting on Ballots SC-081v3.<br><br>While we agree with all proposals related to TLS DV validation and support full automation, we remain unconvinced of the security benefits of further reducing the validity period for OV and EV TLS certificates. </blockquote><p></p><p>It's interesting that the opposition was not to reducing the validity of DV certificates, which can be easily automated, but to OV and EV certificates instead. It's widely known what my views on OV and EV certificates are ([<a href=\"https://scotthelme.co.uk/gone-for-ever/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">1</a>][<a href=\"https://scotthelme.co.uk/are-ev-certificates-worth-the-paper-theyre-written-on/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">2</a>][<a href=\"https://scotthelme.co.uk/looks-like-a-duck-swims-like-a-duck-qwacs-like-a-duck-probably-an-ev-certifiacate/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">3</a>] etc...), but if we're assuming that those certificates have too much manual process involved, and thus require human intervention and are inherently unfriendly to automation, it's really just another nail in the coffin.</p><p></p><blockquote>JPRS abstains from voting on Ballot SC-081.<br><br>We agree with the value of reducing certificate validity periods.<br>We also believe that this ballot indicates to subscribers the inevitability of automation and represents a significant step forward for the WebPKI ecosystem.<br><br>However, we will abstain from this ballot.<br>While we will do our best to support our subscribers and hope that subscribers and other stakeholders in the WebPKI ecosystem will be able to follow the proposed timeline, it is currently uncertain whether it can realistically be achieved.<br><br>In addition, we hope that discussions will take place even after this ballot when necessary, taking into account the actual state of migration in the WebPKI ecosystem.</blockquote><p></p><p>It's good to see JPRS acknowledging the enormous benefit that this ballot brings, and I can also understand and appreciate the hesitation around the ecosystem being ready. Whilst adoption of automated solutions for certificate management has been going well, it's not been going well enough. With that said, there has also never been a requirement for people to adopt those solutions, it was simply a good idea to do so. Now that there is a requirement to have these solutions in place, and a generous 4 year window to get them in place, I'm hoping we start to see an increase in the rate of adoption and progress. </p><p></p><blockquote>TWCA \"ABSTAINS\" from voting on ballot SC-081v3.<br><br>1. We agree that shortening the certificate validity period can reduce cybersecurity risks, and we also acknowledge the advantages of automated certificate management.<br><br>2. We believe that reducing the validity period from 100 days to 47 days is too drastic. Compared to the harm caused by phishing websites, there is currently no way to demonstrate to users how high the cybersecurity risk would be if the certificate validity period were 100 days.<br><br>3. For some websites that already use certificates, if they ultimately fail to deploy automated certificate management, they might abandon HTTPS in favor of HTTP (given that in today’s browser UIs, HTTP only displays a red warning in the upper left corner, whereas improper management leading to certificate expiration results in warnings across the entire page).</blockquote><p></p><p>Again, it's great to see a CA acknowledge the value that these shorter certificates will bring, but after this, I start to struggle with this response. It's really not clear to me what point is being made in comment #2, but I'm not sure what relevance phishing websites have here, and I don't know what cybersecurity risk is present if a certificate is only valid for 100 days! For comment #3, I completely disagree. There's absolutely no reasonable concern that websites are going to move back to HTTP from HTTPS. </p><p></p><blockquote>SECOM Trust Systems ABSTAINS from voting on Ballot SC-081v3.<br><br>We agree with the value of shortening certificate validity periods, and we support this direction in the long term.<br><br>In addition, we believe this ballot signals to subscribers that automation is inevitable, and that this is a great step forward for the WebPKI ecosystem.<br><br>On the other hand, we are also aware that some subscribers may experience costs to adapt.<br><br>In our business practice, we think it is important to be able to explain not only the value but also the cost and explain that our approach is net-positive for the web, unless there is a reasonable alternative.<br><br>Assuming a tradeoff between the period to prepare and the cost, we do not have an answer to shortening it to 47 days instead of 90 days.<br><br>Therefore, we abstain.</blockquote><p></p><p>Lastly, SECOM are another CA acknowledging the enormous benefits that this proposal will bring, but I'm struggling to agree with their argument. It seems that they are suggesting that if certificates were valid for 90 days, then subscribers could/would renew them manually, but a validity period of 47 days would be too short for manual renewal to be practical. I'd make the argument that even at 90 days validity, manual renewal is completely impractical and should already be fully automated, so this doesn't make any real difference at all.</p><p></p><h4 id=\"come-and-geek-out-with-me\">Come and geek out with me</h4><p>If you really want to know more about this whole TLS/PKI ecosystem, why these changes are coming, and the years of history that are driving this, I'd really recommend you check out our training course:</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/image-4.png\" class=\"kg-image\" alt=\"Shorter certificates are coming!\" loading=\"lazy\" width=\"1012\" height=\"466\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/image-4.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/image-4.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/image-4.png 1012w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>There are full details of what we cover on the <a href=\"https://www.feistyduck.com/training/practical-tls-and-pki?ref=scotthelme.co.uk\" rel=\"noreferrer\">training page</a>, but if you want to get hands on with TLS and PKI, fully deploy HTTPS to your own server, including getting a real certificate, build your own CA, and so much more, you should really get involved and join us! It's a fairly intermediate level course, I probably wouldn't recommend it for beginners, but it contains everything you're going to need from a practical perspective, and we have plenty of opportunity to talk about and understand current affairs too.</p><p></p>",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/04/eba75517-388d-4955-8e5f-cb785d86c547.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "TLS",
"term": "TLS",
"url": null
},
{
"label": "PKI",
"term": "PKI",
"url": null
},
{
"label": "Certificate Authorities",
"term": "Certificate Authorities",
"url": null
}
]
},
{
"id": "68021b1ded448d0001f0d432",
"title": "Hacking my Tesla Powerwalls to be the ultimate home energy solution!",
"description": "<p>I've had solar and batteries at home for quite some time now, and despite my experience with them being really awesome, there were a few little things that were bugging me. Using systems from various different suppliers doesn't always provide the perfect integration, so I hacked</p>",
"url": "https://scotthelme.ghost.io/hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/",
"published": "2025-04-21T11:58:28.000Z",
"updated": "2025-04-21T11:58:28.000Z",
"content": "<img src=\"https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\"><p>I've had solar and batteries at home for quite some time now, and despite my experience with them being really awesome, there were a few little things that were bugging me. Using systems from various different suppliers doesn't always provide the perfect integration, so I hacked together my own!</p><p></p><h4 id=\"no-not-that-kind-of-hacking\">No, not that kind of hacking!</h4><p>I have done that kind of hacking over the years, and many times I've blogged about it right here, from <a href=\"https://scotthelme.co.uk/the-vulnerable-web-api-for-my-nissan-leaf/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">cars</a> to <a href=\"https://scotthelme.co.uk/hacking-iot-cameras/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">cctv cameras</a> and <a href=\"https://scotthelme.co.uk/ee-brightbox-router-hacked/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">routers</a> to <a href=\"https://scotthelme.co.uk/hotel-hippo-insecure/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">hotel websites</a>, but this blog post isn't about that type of hacking, it's about what I consider to be the more traditional definition of the term. This is the type of hacking where you get something to perform a function that it wasn't intended to perform, where you take something and make it better than it was by tinkering with it. Think more 'life hack' than 'computer hack', and you're heading along the right lines here. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/image.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"432\" height=\"132\"></figure><p></p><h4 id=\"the-problem\">The problem</h4><p>I have a few different system at home that all are related to power consumption, and whilst they are all 'smart', they don't fully integrate. Here's the overview:</p><p></p><ul><li>☀️Solar Panels: We have 14 Perlight solar panels managed by Enphase that make up the 4.2kWp array on our roof, and they produce energy when the sun shines. This part is pretty straightforward!</li><li>🔋Tesla Powerwalls: We have 3x Tesla Powerwall 2 in our garage that were purchased to help us load-shift our energy usage. Electricity is very expensive in the UK and moving from peak usage which is 05:30 to 23:30 at £0.27/kWh, to off-peak usage, which is 23:30 - 05:30 at £0.07/kWh, is a significant cost saving.</li><li>💡Smart Tariff: My wife and I both drive electric cars and our electricity provider, Octopus Energy, has a Smart Charging tariff. If we plug in one of our cars, and cheap electricity is available, they will activate the charger and allow us to use the off-peak rate, even at peak times. </li></ul><p></p><p>All of this is great, but it does present a few issues:</p><p>1) The main issue is that the Powerwalls will always fill up to 100% on the off-peak rate overnight, aiming to maximise how much power they can supply us during the peak-rate, load shifting the maximum possible amount and saving the most money. That's great, but it doesn't factor in that we have a 4.2kWp solar array on our roof. Once the sun rises and we start generating solar power, the batteries are still full, or almost full, and we quickly begin exporting energy to the grid. Our export tariffs in the UK are terrible and our supplier only pays us a measley £0.04/kWh for exported energy, which is a complete waste! I want a way to tell the Powerwalls to only charge to, let's say, 80% on grid power, and leave headroom for solar generation during the day. No such feature exists. </p><p></p><p>2) The next issue is that the Powerwalls know about our cheap off-peak tariff overnight from 23:30 to 05:30, but not the flexibility of our Smart Charging tariff during peak times. That means that if we plug one of our cars in during the day to top them up, the Powerwalls think we're on peak rates and will charge the car from the batteries and not the grid. I'd like the Powerwalls to be able to respond to our current energy price, rather than a fixed time schedule. No such feature exists. </p><p></p><p>3) The final issue is similar to the above, but about our Powerwall batteries rather than the car batteries. If we have a window of cheap electricity from our electricity supplier, then the Powerwalls should top themselves up when rates are cheap. Not only does this feature not exist, but it also needs to factor in point #1 about not overfilling the batteries and leaving room for solar generation. Argh!</p><p></p><h4 id=\"home-assistant-and-teslemetry-to-the-rescue\">Home Assistant and Teslemetry to the rescue!</h4><p>I've talked about Home Assistant a <em>lot</em> and we use it extensively throughout our house. From lights to appliances, monitoring, alarm systems and more, Home Assistant has a finger in almost every pie! The Tesla Powerwalls integrate nicely with Home Assistant and provide all of their information which I've formulated into a nice dashboard. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1920\" height=\"1080\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-45-50-Tesla-Powerwalls---Home-Assistant.png 1920w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>I can keep a track of the State of Charge (SoC), power coming in and out of the batteries, power coming in and out of the grid, and much more!</p><p></p><p>Of course, we also have an integration for our solar array too, and the Enphase integration allows us to see exactly what's going on with our solar array, and all of the statistics you'd ever want. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1920\" height=\"1026\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-46-34-Solar-Array---Home-Assistant.png 1920w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Finally, our energy supplier has a pretty cool API, and there's an integration for that in Home Assistant too! We can see a whole bunch of different data about our gas and electricity supply, and, crucially, the current rate we're being charged for electricity!</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1920\" height=\"913\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-47-18-Octopus---Home-Assistant.png 1920w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>So, I now have all of the information that I could possibly need in one place, but there was one final piece of the puzzle to solve. The Tesla Powerwall integration in Home Assistant does not allow for control over the Tesla Gateway or Tesla Powerwall devices themselves, it simply retrieves information. No problem, though, because after some quick Googling, I found the <a href=\"https://developer.tesla.com/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">Tesla Fleet API</a>. </p><blockquote>Fleet API is a data and command service providing access to Tesla vehicles and energy devices. Developers can interact with their own devices, or devices for which they have been granted access by a customer.</blockquote><p></p><p>The Fleet API would allow me to have the level of control over my Powerwalls that I needed, and there are a bunch of API clients out there that I could get up and running and build something from. Rather than getting knee deep in building something when my time is a little tight right now, I thought I'd take a look at what solutions exist already, and I quickly found Teslemetry.</p><p></p><p></p>\n<!--kg-card-begin: html-->\n<img src=\"https://scotthelme.co.uk/content/images/2025/04/teslemetry.svg\" class=\"kg-image\" style=\"max-width:400px;\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\">\n<!--kg-card-end: html-->\n<p></p><p></p><h4 id=\"using-teslemetry\">Using Teslemetry</h4><p>The Teslemetry <a href=\"https://teslemetry.com/pricing?ref=scotthelme.ghost.io\" rel=\"noreferrer\">subscription</a> is super cheap, at £2.26/mo including taxes, and it came with a free trial period, and a Home Assistant integration, so it seemed like a winner. At this price it's really not worth spending any time on writing my own code, and I can support the service if it provides a benefit. Getting it set up was a breeze, logging in to my Tesla account and granting it access to only the Powerwalls and not the car, and then hooking that up to the Home Assistant integration. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1920\" height=\"913\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/size/w1600/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1600w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-12-56-00-Settings---Home-Assistant.png 1920w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>As you can see, this is no longer just information coming from the Powerwalls, I now have the ability to control settings on the Powerwalls too! This is what's going to help me create the automations that I need to ensure the Powerwalls behave how I want them to behave.</p><p></p><h4 id=\"capping-the-battery-power-when-grid-charging\">Capping the battery power when grid charging!</h4><p>The first automation I wanted to create was to limit how high the batteries will charge when charging from the grid. The primary benefit here would be to limit the SoC on the overnight charge, so that once the solar starts generating, there would be room in the battery to store than, rather than export it to the grid. There may also be another benefit here in preventing the batteries from hitting 100% SoC and then sitting at 100% SoC for a prolonged period of time, which can be bad for their health over the long term. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1600\" height=\"908\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-23-27-Settings---Home-Assistant.png 1600w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>This automation is set to run every 30 minutes, at the top and bottom of the hour, as that's the time window that my electricity rate can change. Here's the YAML for the automation:</p><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Disable Grid Charging\ndescription: \"\"\ntriggers:\n - trigger: time_pattern\n minutes: /30\nconditions:\n - condition: state\n entity_id: switch.shireburn_allow_charging_from_grid\n state: \"on\"\n - condition: or\n conditions:\n - condition: numeric_state\n entity_id: >-\n sensor.octopus_energy_electricity_1_snip_3_current_rate\n above: 0.11\n - condition: numeric_state\n entity_id: sensor.shireburn_charge\n above: 75\nactions:\n - action: switch.turn_off\n metadata: {}\n data: {}\n target:\n entity_id: switch.shireburn_allow_charging_from_grid\nmode: single\n</code></pre><p></p><p>So, every 30 minutes, I'm checking to see if the cost of electricity is high, or the battery is above 75% SoC, and if so, we're disabling grid charging. This means the battery will only charge when electricity is cheap and the SoC is below the target of 75%. In these summer months where we are producing plenty of solar power, I can keep the target SoC on grid charging at 75%, leaving plenty of room to store solar generation, and in the winter months, I will likely set the target SoC back to 100% as we will have little solar generation and will need all of the battery backup we can get. </p><p>The automation to enable grid charging is then of course very similar, checking for energy cost and SoC target, and then enabling grid charging when required.</p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1597\" height=\"912\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-27-53-Settings---Home-Assistant.png 1597w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>And here's the YAML for that one too:</p><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Enable Grid Charging\ndescription: \"\"\ntriggers:\n - trigger: time_pattern\n minutes: /30\nconditions:\n - condition: state\n entity_id: switch.shireburn_allow_charging_from_grid\n state: \"off\"\n - condition: numeric_state\n entity_id: sensor.shireburn_charge\n below: 75\n - condition: or\n conditions:\n - condition: time\n after: \"23:30:00\"\n before: \"05:30:00\"\n weekday:\n - mon\n - tue\n - wed\n - thu\n - fri\n - sat\n - sun\n - condition: numeric_state\n entity_id: >-\n sensor.octopus_energy_electricity_1_snip_3_current_rate\n below: 0.11\n enabled: true\nactions:\n - action: switch.turn_on\n metadata: {}\n data: {}\n target:\n entity_id: switch.shireburn_allow_charging_from_grid\nmode: single\n</code></pre><p></p><p>This has worked out really well and is doing exactly what we need it to do. If we look at the SoC on the batteries over the last 30 days, you can see when I enabled this automation as they're no longer hitting 100% SoC on the overnight charge, and you can also see a few days before the change when solar managed to charge the batteries back up to 100% and then we ended up exporting to the grid. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"1600\" height=\"722\" srcset=\"https://scotthelme.ghost.io/content/images/size/w600/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 600w, https://scotthelme.ghost.io/content/images/size/w1000/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 1000w, https://scotthelme.ghost.io/content/images/2025/04/Screenshot-2025-04-18-at-13-35-31-History---Home-Assistant.png 1600w\" sizes=\"(min-width: 720px) 720px\"></figure><p></p><p>Apart from some testing on the two nights where I let them charge back to 100% SoC, you can see that the automation is now keeping the batteries at a much better maximum SoC. </p><p></p><h4 id=\"responding-to-dynamic-pricing\">Responding to dynamic pricing</h4><p>When we had the Powerwalls and the solar array installed, we didn't have a dynamic pricing tariff. Our plan was for the solar production and battery power to carry us from 05:30 all the way through to 23:30, completely avoiding the peak costs, and load shifting our entire usage into the off-peak window. That works perfectly well and we don't need to worry about responding to the dynamic pricing from a cost standpoint. </p><p>The other thing that's changed for us is that we now have two electric cars using the EV charger at home. Both of our cars are programmed to only charge during the off-peak window of 23:30 to 05:30, and this hasn't caused any problems for us so far either. We don't do enough driving that we've ever worried about both of us needing to charge at the same time, nor during peak time. The other consideration is that the cars will charge from the Powerwall batteries if they think the grid power is expensive, and ideally we want the cars to draw power from the grid, not the batteries. </p><p>All of that said, it'd still be nice to have the ability to respond to the dynamic pricing and absorb some power from the grid when it's cheap, and charge the cars on grid power rather than battery power if we ever wanted to. The question is, how do I do that? This isn't something the Powerwall is designed to do either.</p><p></p><h4 id=\"utilising-powerwall-settings-to-achieve-our-goal\">Utilising Powerwall settings to achieve our goal</h4><p>Looking at the settings that are surfaced through Teslemetry, there isn't a huge amount of options to go at, so it was easy enough to explore them and see if any of them could be used to do what I need. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/image-1.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"407\" height=\"514\"></figure><p></p><p>I'm already leveraging the 'Allow charging from grid' setting as we've seen above, and the 'Allow Export' and 'Storm Watch' aren't going to be useful here. That leaves the 'Operation Mode' and 'Backup Reserve' options to explore. </p><p></p><figure class=\"kg-card kg-image-card\"><img src=\"https://scotthelme.ghost.io/content/images/2025/04/image-2.png\" class=\"kg-image\" alt=\"Hacking my Tesla Powerwalls to be the ultimate home energy solution!\" loading=\"lazy\" width=\"415\" height=\"582\"></figure><p></p><p>The Operation Mode options can be summarised as:</p><p></p><ul><li>Autonomous - Uses the time settings to load-shift as I've covered so far and does that fully automatically.</li><li>Backup - Saves 100% of the power for backups during grid outage, which isn't really useful to us. </li><li>Self Consumption - The focus is on charging from solar and using 100% of that power to run the house.</li></ul><p></p><p>This means that Autonomous mode is the correct setting for us as it achieves what we set out to achieve with the battery, leaving us the Backup Reserve option to play with, which is the one that sounds like it can do what we need. </p><p>The purpose of Backup Reserve is to set the minimum SoC that the battery will drop down to during operation, always saving the amount specified for grid outages to keep you online. As you can see from my screenshots, we've always had this set to 0% as we want to use the full capacity of the batteries to load shift, and we aren't too concerned about having reserves for if the grid goes down. </p><p>What I found during testing was that if you use the Backup Reserve and Grid Charging options together, you can create some specific behaviour that results in exactly what we need. Here's a table that gives an overview of the scenarios I'm looking at, with the main one highlighted in bold:</p><p></p><table>\n<thead>\n<tr>\n<th>Battery SoC %</th>\n<th>Backup Reserve SoC %</th>\n<th>Grid Charging</th>\n<th>Battery Charging</th>\n<th>Power Draw</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>50%</td>\n<td>0%</td>\n<td>Disabled</td>\n<td>No</td>\n<td>From Battery</td>\n</tr>\n<tr>\n<td>50%</td>\n<td>75%</td>\n<td>Disabled</td>\n<td>No</td>\n<td>From Grid</td>\n</tr>\n<tr>\n<td>50%</td>\n<td>0%</td>\n<td>Enabled</td>\n<td>No</td>\n<td>From Battery</td>\n</tr>\n<tr>\n<td><strong>50%</strong></td>\n<td><strong>75%</strong></td>\n<td><strong>Enabled</strong></td>\n<td><strong>Yes</strong></td>\n<td><strong>From Grid</strong></td>\n</tr>\n<tr>\n<td>80%</td>\n<td>0%</td>\n<td>Disabled</td>\n<td>No</td>\n<td>From Battery</td>\n</tr>\n<tr>\n<td>80%</td>\n<td>75%</td>\n<td>Disabled</td>\n<td>No</td>\n<td>From Battery</td>\n</tr>\n<tr>\n<td>80%</td>\n<td>0%</td>\n<td>Enabled</td>\n<td>No</td>\n<td>From Battery</td>\n</tr>\n<tr>\n<td>80%</td>\n<td>75%</td>\n<td>Enabled</td>\n<td>No</td>\n<td>From Battery</td>\n</tr>\n</tbody>\n</table>\n<p></p><p>By controlling the Backup Reserve %, we can control whether the battery is discharging or not. If the Backup Reserve % is lower than the SoC %, the battery will discharge, and if the Backup Reserve % is higher than the SoC %, the battery will not discharge.</p><p>Alongside that, we can also control if the battery is actively charging from the grid, rather than just allowing our house to draw its power from the grid, using the Grid Charging control. We can now use these combinations of settings to get the Powerwalls to do exactly what we want! In summary:</p><p>The Powerwalls will always charge to 75% when electricity is cheap, meaning we always have spare capacity to absorb solar production, and we can use the car charger any time we like.</p><p></p><p>Here are the YAML configurations for the automations, of which there are now 4, and I've converted them to use variables for the cost and SoC thresholds so they're not hardcoded in the automations:</p><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Disable Grid Charging\ndescription: \"\"\ntriggers:\n - trigger: time_pattern\n minutes: /30\nconditions:\n - condition: state\n entity_id: switch.shireburn_allow_charging_from_grid\n state: \"on\"\n - condition: or\n conditions:\n - condition: numeric_state\n entity_id: >-\n sensor.octopus_energy_electricity_1_snip_3_current_rate\n above: input_number.electricity_cost_threshold\n - condition: numeric_state\n entity_id: sensor.shireburn_charge\n above: input_number.powerwall_soc_threshold\nactions:\n - action: switch.turn_off\n metadata: {}\n data: {}\n target:\n entity_id: switch.shireburn_allow_charging_from_grid\nmode: single\n</code></pre><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Enable Grid Charging\ndescription: \"\"\ntriggers:\n - trigger: time_pattern\n minutes: /30\nconditions:\n - condition: state\n entity_id: switch.shireburn_allow_charging_from_grid\n state: \"off\"\n - condition: numeric_state\n entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate\n below: input_number.electricity_cost_threshold\n enabled: true\n - condition: numeric_state\n entity_id: sensor.shireburn_charge\n below: input_number.powerwall_soc_threshold\nactions:\n - action: switch.turn_on\n metadata: {}\n data: {}\n target:\n entity_id: switch.shireburn_allow_charging_from_grid\nmode: single\n</code></pre><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Set Backup Reserve To 0%\ndescription: \"\"\ntriggers:\n - trigger: time_pattern\n minutes: /30\nconditions:\n - condition: numeric_state\n entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate\n above: input_number.electricity_cost_threshold\n - condition: template\n value_template: \"{{ states('number.shireburn_backup_reserve') | int != 0 }}\"\nactions:\n - action: number.set_value\n metadata: {}\n data:\n value: \"0\"\n target:\n entity_id: number.shireburn_backup_reserve\nmode: single\n</code></pre><p></p><pre><code class=\"language-yaml\">alias: Powerwall - Set Backup Reserve To 75%\ndescription: \"\"\ntriggers:\n - trigger: time_pattern\n minutes: /30\nconditions:\n - condition: numeric_state\n entity_id: sensor.octopus_energy_electricity_1_snip_3_current_rate\n below: input_number.electricity_cost_threshold\n - condition: template\n value_template: >-\n {{states('number.shireburn_backup_reserve') | int !=\n states('input_number.powerwall_soc_threshold') | int}}\nactions:\n - action: number.set_value\n metadata: {}\n data:\n value: \"75\"\n target:\n entity_id: number.shireburn_backup_reserve\nmode: single\n</code></pre><p></p><p>We've had these running for a few days now and, as far as I can see, they seem to be behaving exactly how I want them to behave! That said, if you're a Powerwall expert, Home Assistant expert, or have just spotted something that I can do be doing better, drop by the comments below and let me know!</p><p></p><p><strong><em>Update 13th June 2025</em></strong>: There is now a follow up to this post with further improvements!</p><p><a href=\"https://scotthelme.co.uk/v2-hacking-my-tesla-powerwalls-to-be-the-ultimate-home-energy-solution/?ref=scotthelme.ghost.io\" rel=\"noreferrer\">V2: Hacking my Tesla Powerwalls to be the ultimate home energy solution!</a></p><p></p><p></p>\n<!--kg-card-begin: html-->\n<style>\n pre[class*=\"language-\"],\n code[class*=\"language-\"] {\n font-size: 0.85em !important;\n}\n</style>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/themes/prism-tomorrow.min.css\" integrity=\"sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXthvkSFWOoGGJgS2CW70VK5dQM5Oh+7ne47s74VTg==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\">\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/prism.min.js\" integrity=\"sha512-HiD3V4nv8fcjtouznjT9TqDNDm1EXngV331YGbfVGeKUoH+OLkRTCMzA34ecjlgSQZpdHZupdSrqHY+Hz3l6uQ==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/9000.0.1/components/prism-yaml.min.js\" integrity=\"sha512-epBuSQcDNi/0lmCXr7cGjqWcfnzXe4m/GdIFFNDcQ7v/JF4H8I+l4wmVQiYO6NkLGSDo3LR7HaUfUL/5sjWtXg==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n<!--kg-card-end: html-->",
"image": {
"url": "https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp",
"title": null
},
"media": [
{
"url": "https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp",
"image": "https://scotthelme.ghost.io/content/images/2025/04/b7f4b5a1-2638-4584-9f09-4014d7b50ab1.webp",
"title": null,
"length": null,
"type": "image",
"mimeType": null
}
],
"authors": [
{
"name": "Scott Helme",
"email": null,
"url": null
}
],
"categories": [
{
"label": "Home Assistant",
"term": "Home Assistant",
"url": null
},
{
"label": "Tesla Powerwall",
"term": "Tesla Powerwall",
"url": null
},
{
"label": "Teslemetry",
"term": "Teslemetry",
"url": null
}
]
}
]
}