Let Corporate Tools push the Report IP tool without granting script run

Report IP is an icon on every Public Desktop in the company, so a help desk
caller asking for it to be re-run is asking for something they could already
do themselves at the keyboard. Getting there through ShopDB meant holding
endpoints.runscript, which is the permission that lets someone push ANY script
in the library - the agent self-upgrade included. Handing that over to run a
desktop shortcut is the bundling the permission split was created to take
apart, so the work was simply not being done through the tool.

The allowance is on one named script, never on the run_script action. The
script id rides in the caller-controlled request body, so relaxing the action
would grant the whole library and quietly rebuild the endpoints.deploy bundle.
The enqueue route resolves the target to a real Script row first and matches
the STORED name, because matching what the requester typed would let anyone
name their way past the grant.

Granting on a name is only defensible while the name cannot be made to mean
something else. The body is source-controlled at
plugins/endpoints/api/templates/report-ip-shortcut.ps1, seeded from there,
pinned by SHA-256 on delivery, and the managed fields are re-asserted on every
seed - so an operator cannot flip the row to runcontext=system, deactivate it,
or edit the body and leave the name still pushable.

The template lives under the plugin rather than agent/ because the image copies
plugins/ but not agent/: reading it from agent/ works on a developer checkout
and fails in the deployed container.

The script runs in the signed-in user's session. This is not a preference.
Launched as SYSTEM the tool re-enters the interactive session and the launcher
exits 0 immediately, reporting success having observed nothing - the same
session-0 boundary the collector already documents for mapped drives. User
context fails closed when nobody is logged on, which is the honest answer.

The seed runs in the migrate service alongside the other seeders, so an
existing site picks the script up on the upgrade it already performs.
This commit is contained in:
benjamin.baker
2026-09-16 18:25:48 -04:00
parent 158ee0507e
commit 2ff7b7e906
7 changed files with 448 additions and 7 deletions

View File

@@ -859,6 +859,38 @@ def seed_settings():
click.echo(click.style(f"{created} default settings created.", fg='green'))
@seed_cli.command('shipped-scripts')
@with_appcontext
def seed_shipped_scripts():
"""Seed managed scripts that ship with the build into the script library.
Idempotent: an unchanged template rewrites nothing and leaves the stored
digest alone. Safe to run on every deploy, which is the point - a managed
script row edited in the UI drifts back to the shipped definition here.
Guarded: a lean build without the endpoints plugin has no shipped scripts
to seed, so the command reports that and exits rather than crashing the
seed chain it runs in.
"""
try:
from plugins.endpoints.api import reportipscript
except ImportError:
click.echo('Endpoints plugin is not installed - no shipped scripts to seed.')
return
seeded = reportipscript.seed_report_ip_script()
if seeded['created']:
state = 'created'
elif seeded['updated']:
state = 'updated'
else:
state = 'already current'
click.echo(click.style(
f"{seeded['scriptname']}: {state} "
f"(scriptid={seeded['scriptid']}, {seeded['bytes']} bytes)",
fg='green'))
# Demo assets carry this assetnumber prefix so a re-run skips what it made and
# an operator can bulk-delete them without touching imported/real rows.
DEMO_PREFIX = 'DEMO-'