Files
shopdb-flask/migrations/versions/7d50_script_captureoutput.py
benjamin.baker b49fb60c0c Add log collection, script output capture, and timestamp fixes
Log Collector: a new collect_log action reads a plaintext log file from a
known path and returns its tail for viewing and download. Rides the existing
endpoints.updateapp grant rather than inventing one, since collecting a log is
the same trust level as pushing an application. Collected logs land in their
own table with per-asset retention of 20 instead of overloading the command
audit row, and the agent refuses UNC, traversal, wildcards, directories and
non-text content independently of the server check.

collect_log is POLICY_EXEMPT. Every existing Configuration Policy predates the
action and so cannot name it, and the intersection in ALLOWED_ACTIONS_FOR drops
anything unnamed - so every policied endpoint refused log collection outright,
including for admins, who saw a permission-shaped error for a grant they held.
Exemption from policy is not exemption from permission: the elevated grant
still applies, and a regression test asserts both halves so a future edit
cannot quietly loosen the grant while "fixing" the policy gate.

Script output capture: successful runs can return output, gated per script by
captureoutput. Failures always returned it; success never did, which left a
green badge as the only evidence a run produced.

Timestamps: Activity and Operator Reports rendered date-only because the shared
formatter's 'date' branch drops the clock time. Run Scripts additionally
labels its timestamp, since a bare time meant "completed" on a terminal row but
"queued" on an in-flight one - indistinguishable, and the wrong reading.

Agent 1.16.0. Minor: collect_log is a new action name, so an older agent asked
for it fails closed on the unknown-action default.
2026-09-09 19:23:43 -04:00

49 lines
1.8 KiB
Python

"""Add scripts captureoutput
Backs the per-script "may a successful run send its output back" field.
The agent already captures a script's full output and always writes it to the
endpoint log. What this controls is narrower: whether that output also crosses
to the server and is persisted in the command's resultjson, where the Run
Scripts history can show it.
Failures have always sent output and still do - a failed run has to be
diagnosable without walking to the machine. Success is the case in question,
and it is opt-in because output carries whatever the body happened to print:
hostnames, share paths, an account name, a token an author echoed while
debugging. Accepting that exposure for every routine green run is a poor
trade, so the author decides per script, after reading the body.
BOOLEAN NOT NULL defaulting FALSE, so every script row written before this
migration keeps exactly today's behaviour: metadata on success, output only on
failure. Same rule as errormode - never retroactive, because a library-wide
flip would start persisting output from bodies nobody re-read with this
question in mind.
Revision ID: 7d50_script_captureoutput
Revises: 7d49_script_errormode
Create Date: 2026-09-09
"""
from alembic import op
import sqlalchemy as sa
revision = '7d50_script_captureoutput'
down_revision = '7d49_script_errormode'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('scripts', sa.Column(
'captureoutput', sa.Boolean(), nullable=False,
server_default=sa.false(),
comment='When true, a successful run also returns its output to the '
'server; failures always do. Off by default because script '
'output can contain whatever the body printed.'))
def downgrade():
op.drop_column('scripts', 'captureoutput')