<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <updated>2026-10-16T11:13:45Z</updated>
  <generator>https://njump.me</generator>

  <title>Nostr notes by Ahmed Sozzer (Unofficial)</title>
  <author>
    <name>Ahmed Sozzer (Unofficial)</name>
  </author>
  <link rel="self" type="application/atom+xml" href="https://njump.me/npub1ry634relk898q4875sp0sv8dxv5r9ngmxnsy7wej7n9w99lz2ansx445c8.rss" />
  <link href="https://njump.me/npub1ry634relk898q4875sp0sv8dxv5r9ngmxnsy7wej7n9w99lz2ansx445c8" />
  <id>https://njump.me/npub1ry634relk898q4875sp0sv8dxv5r9ngmxnsy7wej7n9w99lz2ansx445c8</id>
  <icon>https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f9a6.png</icon>
  <logo>https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/1f9a6.png</logo>




  <entry>
    <id>https://njump.me/nevent1qqsfnwnkj25jdqkl8r899pzqyzww7rpy3s507cspuqxzr76z8j6thxqzyqvn2x5087cu5uz5l6jq97psa5ejsvkdrv6wqnemxt6v4c5huftkwq05j5x</id>
    
      <title type="html">Finding the PHP versions in your repos that need the php2shell ...</title>
    
    <link rel="alternate" href="https://njump.me/nevent1qqsfnwnkj25jdqkl8r899pzqyzww7rpy3s507cspuqxzr76z8j6thxqzyqvn2x5087cu5uz5l6jq97psa5ejsvkdrv6wqnemxt6v4c5huftkwq05j5x" />
    <content type="html">
      Finding the PHP versions in your repos that need the php2shell fix&lt;br/&gt;&lt;br/&gt;php.net shipped the fixes for php2shell (CVE-2026-109618 and CVE-2026-109619) on 21 Oct. To patch, you need a list of every PHP you run, and the versions written in your repos mostly can’t give you one. Across 10 public repos, 112 PHP version declarations were labeled before running the tool, and 111 of them can’t prove which PHP build actually runs.&lt;br/&gt;So the work splits in two: find every place a repo declares PHP, then work out which ones you can fix by changing a line and which ones you have to check on a real build.&lt;br/&gt;The fixed versions, and a 60-second check&lt;br/&gt;From php.net’s advisory:&lt;br/&gt;Branch&lt;br/&gt;Fixed in&lt;br/&gt;8.5&lt;br/&gt;8.5.12&lt;br/&gt;8.4&lt;br/&gt;8.4.27&lt;br/&gt;8.3&lt;br/&gt;8.3.36&lt;br/&gt;8.2&lt;br/&gt;8.2.35&lt;br/&gt;8.1 and older get no official release. php.net published source backport branches for 7.4, 8.0 and 8.1 (PHP-8.1-CVE-2026-109618-109619 in php-src, and the same pattern for the other two), and some distros and LTS vendors backport.&lt;br/&gt;The advisory puts the highest risk on installs that serve web requests: PHP-FPM and mod_php first, then php-cgi, FrankenPHP and LiteSpeed. CI runners and dev machines still need the update. They’re just less exposed. If you can’t update yet, the advisory lists mitigations. php.net says they reduce exposure and don’t fix the bug, so I’m linking them instead of copying them here.&lt;br/&gt;To see what a host or image actually runs:&lt;br/&gt;# on a server or container&lt;br/&gt;php -r &amp;#39;echo PHP_VERSION, PHP_EOL;&amp;#39;&lt;br/&gt;dpkg -l &amp;#39;php*&amp;#39; 2&amp;gt;/dev/null | grep ^ii # Debian/Ubuntu packages&lt;br/&gt;apk info -v 2&amp;gt;/dev/null | grep ^php # Alpine packages&lt;br/&gt;# for a built image&lt;br/&gt;docker run --rm --entrypoint php &amp;lt;image&amp;gt; -r &amp;#39;echo PHP_VERSION, PHP_EOL;&amp;#39;&lt;br/&gt;# images on a floating tag only pick up the fix after a rebuild and redeploy&lt;br/&gt;docker build --pull .&lt;br/&gt;For official images and source builds, PHP_VERSION is the answer. For distro packages it isn’t. Debian, Ubuntu and RHEL backport fixes without changing the upstream version, so compare the package version with your vendor’s security tracker. If you install from Remi’s repo, it had no updates as of 21 Oct.&lt;br/&gt;Where PHP versions hide in a repo&lt;br/&gt;That check needs a list of hosts and images to run it on, and the repo is where that list comes from. Each place PHP shows up says something different about the build.&lt;br/&gt;Exact official tags. FROM php:8.3.30-fpm is as close to proof as a repo gets. The docker-library images are built from source at that version, so anything built from this line runs 8.3.30. Whether that’s the image you deployed is a separate question.&lt;br/&gt;Floating tags. FROM php:8.3-fpm resolves at build time. You get the fix once the official image is rebuilt on 8.3.36 and you rebuild with --pull and redeploy. Restarting the same container keeps the old build, and so does reusing a tag with imagePullPolicy: IfNotPresent.&lt;br/&gt;setup-php. php-version: &amp;#39;8.3.30&amp;#39; looks like a pin. setup-php installs whatever 8.3 patch is current or already on the runner, so a version at or above the fix can still run an older build. The first draft of the tool’s spec treated these as exact pins. The tool treats them as floating.&lt;br/&gt;Version files. asdf installs exactly what .tool-versions says, so php 8.4.20 there is a real pin. .php-version is different: setup-php, Symfony CLI and Herd read it and use whatever patch is installed.&lt;br/&gt;Distro packages. apt-get install php8.2-cli gets you whatever the vendor ships, and the version string won’t show a backported fix.&lt;br/&gt;Images that bundle PHP. wordpress, drupal, nextcloud and Laravel Sail runtimes have PHP inside and no PHP version on the FROM line. The fix arrives when the maintainer rebuilds and you pull.&lt;br/&gt;Composer constraints. &amp;#34;php&amp;#34;: &amp;#34;^8.2&amp;#34; tells Composer what the code supports. It doesn’t pick the runtime. Updating Laravel, Symfony or WordPress code doesn’t change the runtime either, since php2shell is in PHP itself.&lt;br/&gt;EOL branches. 8.1 and older need action however they’re declared: upgrade, take a vendor backport, or build from php.net’s backport branch.&lt;br/&gt;What I measured&lt;br/&gt;The corpus is 10 public PHP repos, each pinned to a commit SHA. The labels were made by reading each repo’s files line by line before running the tool on it, with an AI coding assistant, in one labeling pass. They’re recorded in results/labels.json, line level. Recall only covers the file types the labeling grep covered: Dockerfiles, Containerfiles, YAML, .php-version, .tool-versions and composer.json. The baseline is grep -rnE &amp;#34;FROM &#43;php:&amp;#34;, the usual “inventory your Dockerfiles” step. I’m not publishing per-repo verdicts.&lt;br/&gt;Six repos were the tuning set. On the first run the tool found 58 of 69 labeled declarations, with 3 false positives, and grep found 31. The miss worth knowing about even if you never run the tool: setup-php versions that aren’t written next to setup-php. Some were set under env: and read back as ${{ env.php-version }}, and some were passed as an input to a reusable workflow (with: php-versions: &amp;#34;[&amp;#39;8.3&amp;#39;, &amp;#39;8.4&amp;#39;]&amp;#34;). Search a workflow for setup-php and you’ll find the step, but the version lives somewhere else.&lt;br/&gt;After fixing those gaps, the tuning set went to 70 of 70. That’s tuned on the same repos, so it doesn’t count. (It’s 70 now because the labeling pass missed one declaration, which was added after the first run.) Then 4 more repos were labeled the same way and measured once:&lt;br/&gt;Held out: 4 repos, 42 labels, one run&lt;br/&gt;Found&lt;br/&gt;False positives&lt;br/&gt;php2shell-repo-audit&lt;br/&gt;36 / 42 (86%)&lt;br/&gt;1&lt;br/&gt;grep -rnE &amp;#34;FROM &#43;php:&amp;#34;&lt;br/&gt;16 / 42 (38%)&lt;br/&gt;0&lt;br/&gt;The 6 held-out misses were setup-php referenced by full URL (uses: &lt;a href=&#34;https://github.com/shivammathur/setup-php@v2&#34;&gt;https://github.com/shivammathur/setup-php@v2&lt;/a&gt;, common on Forgejo), an Arch pacman -S install, and Debian’s libapache2-mod-php. That last one is mod_php, one of the two highest-risk setups in the advisory, and its package name doesn’t start with php. All 6 are fixed in the release, so 36 of 42 measures the code from before those fixes.&lt;br/&gt;Across all 10 repos, 111 of the 112 labeled declarations can’t prove which PHP build runs: floating tags, setup-php, distro packages, Sail runtimes, bundled images, templated tags and composer constraints. The one exception is a .tool-versions file with a patch version. Against php.net’s fix table, a naive version compare gives a firm “vulnerable” or “fixed” on all 95 version-dependent, non-EOL findings whose source can’t prove the runtime. Each of those is a guess.&lt;br/&gt;It’s a small corpus with one labeling pass. Every run is in results/runs/, with the method in results/numbers.md.&lt;br/&gt;The tool&lt;br/&gt;php2shell-repo-audit reads those files and puts each declaration in ACTION REQUIRED, CHECK MANUALLY or DECLARED ≥ FIXED, with a reason. It never calls a repo fine: DECLARED ≥ FIXED always means “runtime not verified”. It needs Node 20 or newer and has no runtime dependencies.&lt;br/&gt;npx php2shell-repo-audit@0.1.0 path/to/repo&lt;br/&gt;npx php2shell-repo-audit@0.1.0 --org your-org --markdown &amp;gt; php2shell.md&lt;br/&gt;--org reads default branches with a token from GH_TOKEN, GITHUB_TOKEN or gh auth token, GET requests only, and skips archived repos and forks. It only sees what the token can read, so check the repo count it prints.&lt;br/&gt;Here’s the published 0.1.0 on the demo repo, which is synthetic: a made-up repo with 7 planted declarations.&lt;br/&gt;$ npx php2shell-repo-audit@0.1.0 demo-repo&lt;br/&gt;php2shell-repo-audit 0.1.0 · fix table rev 2: published 2026-10-21, &lt;a href=&#34;https://www.php.net/archive/2026.php#2026-10-21-5&#34;&gt;https://www.php.net/archive/2026.php#2026-10-21-5&lt;/a&gt; (remote)&lt;br/&gt;Reads PHP versions declared in this repo. It does not check running servers.&lt;br/&gt;ACTION REQUIRED (3)&lt;br/&gt;.tool-versions:2 dev php 8.4.20 vulnerable: fixed in 8.4.27, upgrade&lt;br/&gt;docker/api/Dockerfile:1 prod FROM php:8.3.30-fpm vulnerable: fixed in 8.3.36, upgrade&lt;br/&gt;docker/legacy/Dockerfile:1 prod FROM php:8.1-fpm eol: no official php.net release for 8.1; upgrade, use your vendor&amp;#39;s backport, or build from php.net&amp;#39;s backport branch PHP-8.1-CVE-2026-109618-109619&lt;br/&gt;CHECK MANUALLY (4)&lt;br/&gt;.github/workflows/ci.yml:10 ci setup-php 8.3 floating-rebuild: setup-php installs the current or pre-installed 8.3 patch, not a pinned build&lt;br/&gt;Dockerfile:1 prod FROM php:8.3-fpm floating-rebuild: rebuild with --pull and redeploy after the release, then confirm with the docker run check&lt;br/&gt;composer.json:6 prod &amp;#34;php&amp;#34;: &amp;#34;^8.2&amp;#34; constraint-only: a composer constraint is not the runtime&lt;br/&gt;docker/worker/Dockerfile:3 prod apt-get install php8.2-cli distro-vendor: vendors backport fixes without changing the version; check your vendor&amp;#39;s security tracker&lt;br/&gt;DECLARED ≥ FIXED (0)&lt;br/&gt;The docker run check: docker run --rm --entrypoint php &amp;lt;image&amp;gt; -r &amp;#39;echo PHP_VERSION;&amp;#39;&lt;br/&gt;exit 1: action required&lt;br/&gt;grep finds 3 of those 7 lines. The two exact pins below the fix and the 8.1 image get a firm verdict. Everything the repo can’t settle goes to CHECK MANUALLY with the thing to check.&lt;br/&gt;As a GitHub Action:&lt;br/&gt;- uses: actions/checkout@v4&lt;br/&gt;- uses: Amsozzer1/php2shell-repo-audit@v0.1.0&lt;br/&gt;with:&lt;br/&gt;path: .&lt;br/&gt;fail-on: manual # or &amp;#34;action&amp;#34; to fail only on ACTION REQUIRED&lt;br/&gt;It writes the findings to the job summary. By default it fails on ACTION REQUIRED, on anything that needs a manual check, and when it finds no PHP at all. A green job means nothing this repo declares needs attention, and says nothing about the runtime.&lt;br/&gt;What it doesn’t see&lt;br/&gt;It reads git. That leaves out:&lt;br/&gt;Servers and shared hosting that aren’t described in a repo: cPanel, Plesk, .htaccess PHP handlers.&lt;br/&gt;PaaS and managed runtimes such as Platform.sh, Upsun, Laravel Vapor, Bref, Forge and Heroku. The README has the full list.&lt;br/&gt;Helm and Kustomize templates, compose extends:, GitLab include:, and versions set in .env files.&lt;br/&gt;The image you actually deployed, and anything built outside the repo. That’s what the docker run line is for.&lt;br/&gt;With --org, anything that isn’t on a default branch. A service built from another branch or an old tag won’t be in the list.&lt;br/&gt;The next thing I’d build is an inspect &amp;lt;image&amp;gt; command that runs php inside an image and checks its distro packages, so CI can check the build instead of the file that describes it. Until that exists, the repo gives you the list of things to rebuild, and the docker run line is still the only step here that looks at a real build.&lt;br/&gt;&lt;br/&gt;&lt;a href=&#34;https://amsozzer.com/writing/finding-the-php-versions-that-need-the-php2shell-fix&#34;&gt;https://amsozzer.com/writing/finding-the-php-versions-that-need-the-php2shell-fix&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;#php #phpversions #fix #ubuntu #branchfixed&lt;br/&gt;&lt;br/&gt;#rss #feeds #linksharing #qwibbit
    </content>
    <updated>2026-10-22T02:43:35Z</updated>
  </entry>

  <entry>
    <id>https://njump.me/nevent1qqs2zpzh3uqg3ym3d62f3jwkz7c9waqpy99marwlj4hndzp9lw9zndczyqvn2x5087cu5uz5l6jq97psa5ejsvkdrv6wqnemxt6v4c5huftkwl7v0gk</id>
    
      <title type="html">How often public Supabase repos leave a table readable by the ...</title>
    
    <link rel="alternate" href="https://njump.me/nevent1qqs2zpzh3uqg3ym3d62f3jwkz7c9waqpy99marwlj4hndzp9lw9zndczyqvn2x5087cu5uz5l6jq97psa5ejsvkdrv6wqnemxt6v4c5huftkwl7v0gk" />
    <content type="html">
      How often public Supabase repos leave a table readable by the anon key&lt;br/&gt;&lt;br/&gt;While going through some of my older projects, I found a Supabase table with row level security off. It came in with a PR I’d mostly vibe coded, and I’d never turned RLS on for it. Anyone holding the anon key could read it, and the anon key ships in the frontend bundle. I fixed it as soon as I saw it.&lt;br/&gt;Then I wanted to know how common that is. So I replayed the migration history of 1,000 public Supabase repos into throwaway local databases and checked every table, commit by commit.&lt;br/&gt;Short version: 18.8% of the repos I could replay had a fully readable table at some point, and 10.9% still have one at their latest commit.&lt;br/&gt;Why this happens&lt;br/&gt;A Supabase frontend talks to Postgres through PostgREST, using the anon key. Supabase’s default privileges grant anon SELECT, INSERT, UPDATE and DELETE on new tables in public. Row level security is the only thing standing between those grants and your rows.&lt;br/&gt;The dashboard’s table editor turns RLS on for new tables by default. A create table in a migration file doesn’t:&lt;br/&gt;create table public.messages (&lt;br/&gt;id bigint generated always as identity primary key,&lt;br/&gt;body text not null&lt;br/&gt;);&lt;br/&gt;-- no &amp;#34;alter table public.messages enable row level security;&amp;#34;&lt;br/&gt;-- anon can now read and write every row&lt;br/&gt;So the same table is safe or open depending on whether you made it in the UI or in SQL.&lt;br/&gt;What I measured&lt;br/&gt;Sample. GitHub code search for .sql files under supabase/migrations/, sliced into 8 file-size bands to get past the API’s 1,000-result cap. Every band hit the cap, so the frame is the first 1,000 hits in each band: 5,865 repos. I screened them in seeded random order and kept the first 1,000 that weren’t forks, templates, tutorials or Supabase’s own examples, and that had at least 3 migration files and 2 commits touching them.&lt;br/&gt;Replay. For each repo, every commit that touched the migrations or seed.sql, capped at 30 (first, last and evenly spaced in between). Each commit was replayed into a fresh supabase/postgres container with no outbound network, using the same rules supabase db push uses. Nothing touched a live project, and no URL was visited.&lt;br/&gt;Exposure. A table in public with RLS off, on which anon has table-level SELECT. That means every row and every column is readable with the public key.&lt;br/&gt;Results&lt;br/&gt;Repos&lt;br/&gt;Share&lt;br/&gt;95% CI&lt;br/&gt;Exposed at any sampled commit&lt;br/&gt;146 of 777&lt;br/&gt;18.8%&lt;br/&gt;16.2 to 21.7%&lt;br/&gt;Still exposed at the latest commit&lt;br/&gt;43 of 393&lt;br/&gt;10.9%&lt;br/&gt;8.2 to 14.4%&lt;br/&gt;The denominators differ because not every commit replays (more on that below). 777 repos had at least one commit that replays, and 393 had a latest commit that does.&lt;br/&gt;When an exposure ended, it usually wasn’t a quick fix in the next commit. 67% of the ones that ended lasted more than a day and 37% more than a week. Counting exposures that are still open, the median was 47 days (Kaplan–Meier, since open ones haven’t finished yet). 22 of the 75 that ended did so at the very next migration commit.&lt;br/&gt;How long exposures lasted, per repo, ended and still open&lt;br/&gt;Ended exposures, 75 in all: under 1 hour 8, 1 hour to 1 day 17, 1 to 7 days 22, 7 to 30 days 9, 30 to 90 days 13, 90 to 365 days 5, over 1 year 1. Still open, time so far, 86 in all: under 1 hour 17, 1 hour to 1 day 11, 1 to 7 days 8, 7 to 30 days 12, 30 to 90 days 17, 90 to 365 days 20, over 1 year 1.&lt;br/&gt;Under 1 hour&lt;br/&gt;8 ended&lt;br/&gt;17 still open&lt;br/&gt;1 hour to 1 day&lt;br/&gt;17&lt;br/&gt;11&lt;br/&gt;1 to 7 days&lt;br/&gt;22&lt;br/&gt;8&lt;br/&gt;7 to 30 days&lt;br/&gt;9&lt;br/&gt;12&lt;br/&gt;30 to 90 days&lt;br/&gt;13&lt;br/&gt;17&lt;br/&gt;90 to 365 days&lt;br/&gt;5&lt;br/&gt;20&lt;br/&gt;Over 1 year&lt;br/&gt;1&lt;br/&gt;1&lt;br/&gt;How long exposures lasted, per repo. ‘Still open’ is the time so far at the last commit that replays. 161 exposures in 146 repos.&lt;br/&gt;A few results worth a closer look&lt;br/&gt;Starred repos did worse. Repos with 1 to 9 stars were exposed at their latest commit 20.3% of the time, against 8.3% for repos with no stars, and the intervals don’t overlap. I wouldn’t read that as cause. Starred repos tend to have longer histories, and every extra commit is another chance to add a table.&lt;br/&gt;Older repos weren’t safer. Repos created before 2026 were at 16.7% at their latest commit and 2026 repos at 9.5%, and that difference is within the intervals. If you wanted to pin this on AI coding tools, this data doesn’t support it.&lt;br/&gt;Most latest commits don’t replay at all. Only 39% of repos had a latest commit that applies cleanly on a fresh database. Of the ones that fail, 69% fail because a migration refers to a table, column or function that doesn’t exist yet, almost always because it was made in the dashboard. For those projects, the migration folder can’t rebuild the database it’s supposed to describe.&lt;br/&gt;Public reads are everywhere, and that’s mostly fine. 48.6% of repos had a SELECT policy for anon with using (true) on a table that does have RLS on. That’s how a public profile page or a blog works. Supabase’s own linter skips that pattern on purpose and only flags always-true write policies, which showed up in 9.3% of repos.&lt;br/&gt;Limits&lt;br/&gt;Migrations aren’t the live database. A project can turn RLS on in the dashboard without a migration. Then it looks exposed here when it isn’t. The reverse happens too.&lt;br/&gt;The sample is young and small. 72% of the repos were created in 2026 and 79% have no stars. Public repos lean toward hobby, demo and portfolio projects even after filtering.&lt;br/&gt;The frame is whatever order GitHub search returns. Repos outside those 8 size bands, or past the first 1,000 hits in a band, had no chance of being picked.&lt;br/&gt;The 30-commit cap can catch an exposure’s start late. I replayed 25 capped repos again with no cap (3,026 commits). No repo changed classification, and start times moved by at most about a day.&lt;br/&gt;The 43&lt;br/&gt;42 of the 43 repos still exposed at their latest commit got a private note, by email, GitHub’s private vulnerability reporting, or an issue asking for a security contact (with no details in it). The last one has no private contact channel and doesn’t look deployed, so I left it alone. The published data replaces repo names with repo-NNN and table names with salted hashes, because a distinctive table name can be searched back to its repo. As of October 15, one owner has confirmed a fix.&lt;br/&gt;Checking your own project&lt;br/&gt;In the dashboard, Advisors &amp;gt; Security flags these as “RLS disabled in public”. The fix is one line per table, plus policies for whatever access the app actually needs:&lt;br/&gt;alter table public.messages enable row level security;&lt;br/&gt;To catch it before it merges, I built Lintel, which replays a PR’s migrations and fails the check when the PR adds a table anon can read:&lt;br/&gt;npx @amsozzer/lintel migrations check&lt;br/&gt;There’s also a playground that runs the same checks in your browser.&lt;br/&gt;The method, the scripts and the anonymized data (one row per sampled commit and per exposure) are in the repo: study/results.md.&lt;br/&gt;&lt;br/&gt;&lt;a href=&#34;https://amsozzer.com/writing/how-often-public-supabase-repos-leave-a-table-readable&#34;&gt;https://amsozzer.com/writing/how-often-public-supabase-repos-leave-a-table-readable&lt;/a&gt;
    </content>
    <updated>2026-10-16T02:41:02Z</updated>
  </entry>

</feed>