ANT-2026-XKHXVCFV · supabase/postgres
other low
Severity Claude low · Security research firm - · Maintainer -
Discovered by Claude Mythos Preview
Anthropic's analysis of this finding, sealed at approval.
ANT-2026-XKHXVCFV: Root-executed binaries fetched from S3 without integrity check
Several Ansible tasks (admin-mgr.yml:11, admin-api.yml:40-44, supabase-admin-agent.yml:51-68) use get_url to pull tarballs from https://supabase-public-artifacts-bucket.s3.amazonaws.com/ and install the contents as root-run binaries (admin-mgr is a NOPASSWD sudo target; supabase-admin-agent runs as root via systemd). None of these get_url calls set the checksum: parameter, so nothing verifies the downloaded artifact matches what the authors published. An attacker who can write to that S3 bucket, or MITM the image-build network with a trusted CA, can substitute a trojaned tarball. The result is root code execution on every AMI/qcow2 image built after the substitution.
Target
Project: supabase/postgres
Location: ansible/tasks/internal/admin-mgr.yml:11
Discovery: static analysis — not yet dynamically reproduced
Technical Details
The get_url tasks omit the checksum: parameter, so integrity of the fetched tarball depends solely on TLS transport and the security of the S3 bucket. Because the extracted binaries are installed into root-privileged execution paths (NOPASSWD sudoers entry for /usr/bin/admin-mgr and a root systemd unit for supabase-admin-agent), any tampering with the artifact translates directly into root on the target instance.
Reproduction
- Obtain write access to supabase-public-artifacts-bucket (or establish a TLS-trusted MITM on the build network).
- Replace a referenced tarball (e.g. supabase-admin-api__linux_arm64.tar.gz) with a trojaned build.
- Wait for the next AMI/qcow2 image build; Ansible get_url fetches the tarball without checksum verification.
- Trojaned binary is installed at /opt/supabase-admin-api / /usr/bin/admin-mgr and executed as root via systemd and NOPASSWD sudoers.
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Suggested Fix
Pin every downloaded executable by cryptographic hash (add checksum: to each get_url task) or verify a detached signature at install time, independent of transport security.
Acknowledgement
This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-XKHXVCFV.
Reference: ANT-2026-XKHXVCFV
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure
The change that resolved this finding.
diff --git a/ansible/files/adminapi.service.j2 b/ansible/files/adminapi.service.j2
index 305d1ac629..4e956703a5 100644
--- a/ansible/files/adminapi.service.j2
+++ b/ansible/files/adminapi.service.j2
@@ -14,7 +14,7 @@ Restart=always
RestartSec=3
TimeoutStopSec=10
Environment="AWS_USE_DUALSTACK_ENDPOINT=true"
-{% if qemu_mode is defined and qemu_mode %}
+{% if qemu %}
Environment="AWS_SDK_LOAD_CONFIG=true"
{% endif %}
StandardOutput=journal
diff --git a/ansible/files/gotrue.service.j2 b/ansible/files/gotrue.service.j2
index 481ce7c986..4105e92c66 100644
--- a/ansible/files/gotrue.service.j2
+++ b/ansible/files/gotrue.service.j2
@@ -90,7 +90,7 @@ Environment=GOTRUE_RELOADING_POLLER_ENABLED=false
# ensures only 1 reload operation occurs during a burst of config updates.
Environment=GOTRUE_RELOADING_GRACE_PERIOD_INTERVAL=2s
-{% if qemu_mode is defined and qemu_mode %}
+{% if qemu %}
# v3 does not use filesystem notifications for config reloads.
Environment=GOTRUE_RELOADING_NOTIFY_ENABLED=false
{% else %}
diff --git a/ansible/files/postgres_exporter.service.j2 b/ansible/files/postgres_exporter.service.j2
index dcb107cb77..4cf067a9a2 100644
--- a/ansible/files/postgres_exporter.service.j2
+++ b/ansible/files/postgres_exporter.service.j2
@@ -6,7 +6,7 @@ StartLimitBurst=0
[Service]
Type=simple
-ExecStart=/opt/postgres_exporter/postgres_exporter --disable-settings-metrics --extend.query-path="/opt/postgres_exporter/queries.yml" --disable-default-metrics --no-collector.locks --no-collector.replication --no-collector.replication_slot --no-collector.stat_bgwriter --no-collector.stat_database --no-collector.stat_user_tables --no-collector.statio_user_tables --no-collector.wal {% if qemu_mode is defined and qemu_mode %}--no-collector.database {% endif %}
+ExecStart=/opt/postgres_exporter/postgres_exporter --disable-settings-metrics --extend.query-path="/opt/postgres_exporter/queries.yml" --disable-default-metrics --no-collector.locks --no-collector.replication --no-collector.replication_slot --no-collector.stat_bgwriter --no-collector.stat_database --no-collector.stat_user_tables --no-collector.statio_user_tables --no-collector.wal {% if qemu %}--no-collector.database {% endif %}
User=postgres
Group=postgres
diff --git a/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.service.j2 b/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.service.j2
index d93d87dfa4..d5647c8865 100644
--- a/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.service.j2
+++ b/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.service.j2
@@ -6,7 +6,7 @@ Requires=local-fs.target
[Service]
Type=oneshot
-{% if qemu_mode is defined and qemu_mode %}
+{% if qemu %}
ExecStart=/opt/supabase-admin-agent/supabase-admin-agent --config /opt/supabase-admin-agent/config.yaml salt --apply --store-result --targets-key targetsv3-main.yaml
User=root
Group=root
diff --git a/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.timer.j2 b/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.timer.j2
index 390e6da570..52a4a81558 100644
--- a/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.timer.j2
+++ b/ansible/files/supabase_admin_agent_config/supabase-admin-agent_salt.timer.j2
@@ -4,7 +4,7 @@ Requires=supabase-admin-agent_salt.service
[Timer]
{# We're using a significantly lower frequency for triggering this agent on qemu images for the moment. Once we've performed additional validations re: the aggregate impact of running it more frequently, the frequency can be increased. #}
-{% if qemu_mode is defined and qemu_mode %}
+{% if qemu %}
OnCalendar=*-*-* 0,6,12,18:00:00
RandomizedDelaySec={{ supabase_admin_agent_splay }}
AccuracySec=1h
diff --git a/ansible/playbook.yml b/ansible/playbook.yml
index 488dcc26fe..1a2543244a 100644
--- a/ansible/playbook.yml
+++ b/ansible/playbook.yml
@@ -2,6 +2,10 @@
become: yes
pre_tasks:
+ - name: Set the platform arch as a fact
+ ansible.builtin.set_fact:
+ platform: "{{ 'amd64' if ansible_facts['architecture'] == 'x86_64' else 'arm64' }}"
+
- import_tasks: tasks/setup-system.yml
vars_files:
- ./vars.yml
@@ -40,124 +44,119 @@
tags:
- install-pgbouncer
- install-supabase-internal
- when: nixpkg_mode or stage2_nix
- name: Install WAL-G
import_tasks: tasks/setup-wal-g.yml
- when: nixpkg_mode or stage2_nix
- name: Install pgBackRest
- import_tasks:
+ import_tasks:
file: tasks/setup-pgbackrest.yml
- when:
- - nixpkg_mode or stage2_nix
- name: Install Gotrue
+ when: not stage2
import_tasks: tasks/setup-gotrue.yml
tags:
- install-gotrue
- install-supabase-internal
- when: nixpkg_mode
-
+
- name: Install PostgREST
+ when: not stage2
import_tasks: tasks/setup-postgrest.yml
tags:
- install-postgrest
- install-supabase-internal
- when: nixpkg_mode
- name: Install Envoy
+ when: not stage2
import_tasks: tasks/setup-envoy.yml
tags:
- install-supabase-internal
- when: nixpkg_mode
- name: Install Kong
+ when: not stage2
import_tasks: tasks/setup-kong.yml
tags:
- install-supabase-internal
- when: nixpkg_mode
- name: Install nginx
+ when: not stage2
import_tasks: tasks/setup-nginx.yml
tags:
- install-supabase-internal
- when: nixpkg_mode
- name: Install Supabase specific content
+ when: not stage2
import_tasks: tasks/setup-supabase-internal.yml
tags:
- install-supabase-internal
- when: nixpkg_mode
- name: Fix IPv6 NDisc issues (disabled)
+ when: not stage2 and not qemu
import_tasks: tasks/fix-ipv6-ndisc.yml
tags:
- install-supabase-internal
- when: nixpkg_mode and (qemu_mode is undefined)
- name: Adjust APT update intervals
+ when: not stage2
copy:
src: files/apt_periodic
dest: /etc/apt/apt.conf.d/10periodic
- when: nixpkg_mode
-
+
- name: Transfer init SQL files
+ when: stage2
copy:
src: files/{{ item.source }}
dest: /tmp/{{ item.dest }}
loop: "{{ sql_files }}"
- when: stage2_nix
- name: Create postgres role
+ when: stage2
become: yes
become_user: postgres
shell:
cmd: /usr/lib/postgresql/bin/psql --username=supabase_admin -d postgres -c "create role postgres superuser login; alter database postgres owner to postgres;"
- when: stage2_nix
- name: Execute init SQL files
+ when: stage2
become: yes
become_user: postgres
shell:
cmd: /usr/lib/postgresql/bin/psql -f /tmp/{{ item.dest }}
loop: "{{ sql_files }}"
- when: stage2_nix
- name: Delete SQL scripts
+ when: stage2
file:
path: /tmp/{{ item.dest }}
state: absent
loop: "{{ sql_files }}"
- when: stage2_nix
- name: First boot optimizations
+ when: stage2
import_tasks: tasks/internal/optimizations.yml
tags:
- install-supabase-internal
- when: stage2_nix
-
+
- name: Finalize AMI
+ when: not stage2
import_tasks: tasks/finalize-ami.yml
tags:
- install-supabase-internal
- when: nixpkg_mode
-
+
- name: Enhance fail2ban
+ when: not stage2
import_tasks: tasks/setup-fail2ban.yml
- when: nixpkg_mode
-
# Install EC2 instance connect
# Only for AWS images
- name: install EC2 instance connect
+ when: not qemu
become: yes
apt:
pkg:
- ec2-instance-connect
tags:
- aws-only
- when: qemu_mode is undefined
# Install this at the end to prevent it from kicking in during the apt process, causing conflicts
- name: Install security tools
@@ -172,53 +171,53 @@
import_tasks: tasks/clean-build-dependencies.yml
- name: Run migrations
+ when: sta
… (truncated)https://github.com/supabase/postgres/commit/aae1bedc9d6906ba329529ad2435ba124c428a7e
Recorded dates, in order.
- 2026-05-10 Discovered or logged
- 2026-05-15 Sent to maintainer
- 2026-05-15 Maintainer acknowledged
- 2026-07-29 Patch released
- 2026-08-18 Publicly revealed
SHA-3-512 hash:
1b5a9a965ae05bb37cfce6d4b4b02c96f9fc8d5224f919cb1295890545119c82fa29b858b54e2114cfeb8cd8bd88b85f6fe03325b6edf61cbf01ae6b3b91197c
Committed 2026-05-18 00:56 UTC
Revealed 2026-08-18 14:12 UTC
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-XKHXVCFV",
"bug_class": "other",
"claude_severity": "low",
"commit_sha": null,
"created_at": "2026-05-14T22:03:28+00:00",
"description": "Several Ansible tasks (admin-mgr.yml:11, admin-api.yml:40-44, supabase-admin-agent.yml:51-68) use get_url to pull tarballs from https://supabase-public-artifacts-bucket.s3.amazonaws.com/ and install the contents as root-run binaries (admin-mgr is a NOPASSWD sudo target; supabase-admin-agent runs as root via systemd). None of these get_url calls set the checksum: parameter, so nothing verifies the downloaded artifact matches what the authors published. An attacker who can write to that S3 bucket, or MITM the image-build network with a trusted CA, can substitute a trojaned tarball. The result is root code execution on every AMI/qcow2 image built after the substitution.",
"discovered_at": "2026-05-10T00:00:00+00:00",
"location": "ansible/tasks/internal/admin-mgr.yml:11",
"poc_sha256": null,
"preimage_version": 1,
"project": "supabase/postgres",
"reproduction": [
"1. Obtain write access to supabase-public-artifacts-bucket (or establish a TLS-trusted MITM on the build network).",
"2. Replace a referenced tarball (e.g. supabase-admin-api_<ver>_linux_arm64.tar.gz) with a trojaned build.",
"3. Wait for the next AMI/qcow2 image build; Ansible get_url fetches the tarball without checksum verification.",
"4. Trojaned binary is installed at /opt/supabase-admin-api / /usr/bin/admin-mgr and executed as root via systemd and NOPASSWD sudoers."
],
"technical_details": "The get_url tasks omit the `checksum:` parameter, so integrity of the fetched tarball depends solely on TLS transport and the security of the S3 bucket. Because the extracted binaries are installed into root-privileged execution paths (NOPASSWD sudoers entry for /usr/bin/admin-mgr and a root systemd unit for supabase-admin-agent), any tampering with the artifact translates directly into root on the target instance.",
"title": "Root-executed binaries fetched from S3 without integrity check",
"vendor_severity": null
}