Promote the 2026-08-30 review security fixes and supplies ordering to main

This commit is contained in:
2026-08-31 09:24:13 -04:00
parent 6680c3c5de
commit 0e3d5a6226
30 changed files with 991 additions and 98 deletions

View File

@@ -0,0 +1,51 @@
"""Pin the approved content digest on an endpoint command.
Delivery descriptors (run_script, update_app) are re-resolved when the agent
claims a command, not when an operator queues it. Between those two moments the
underlying script body or installer package can be replaced by anyone who can
edit the applications catalog, and the descriptor's SHA-256 would then describe
the NEW bytes: the agent verifies correctly and runs something the operator
never approved.
pinnedsha256 records the digest as it stood at enqueue. The claim path compares
and refuses the command if it has moved, so a swap fails the push instead of
shipping it.
Null for actions that deliver no content, and null on rows queued before this
migration - those are treated as unpinned and claim as before.
Idempotent: checks the live schema first so a db.create_all() site re-runs clean
on strict MySQL 8.
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'endpoints0005cmdpin'
down_revision = 'endpoints0004apps'
branch_labels = None
depends_on = None
def _has_column(table_name, column_name):
from sqlalchemy import inspect
inspector = inspect(op.get_bind())
if not inspector.has_table(table_name):
return False
return any(col['name'] == column_name
for col in inspector.get_columns(table_name))
def upgrade():
if not _has_column('endpointcommands', 'pinnedsha256'):
op.add_column('endpointcommands',
sa.Column('pinnedsha256', sa.String(length=64),
nullable=True))
def downgrade():
if _has_column('endpointcommands', 'pinnedsha256'):
op.drop_column('endpointcommands', 'pinnedsha256')