Files
shopdb-flask/plugins/endpoints/frontend/views/systemScriptsSettings.spec.js
benjamin.baker fd18099199 Gate the script library on the grant that pushes scripts
The script library was gated on applications.*, so anyone holding the
software-delivery grant could rewrite the bytes of a script that a separately
trusted operator later pushed to a bay. The push was gated on
endpoints.runscript; the CONTENT of what got pushed was not. An installer is a
vendor artefact, but a script here is arbitrary PowerShell this site wrote that
the agent runs as SYSTEM, so the write and the push must require the same
trust.

All six script routes now require endpoints.runscript, including the body
download - reading the bytes is not a lesser act than listing them. Core names
the permission as a string rather than importing it from the endpoints plugin
so the module stays importable with the plugin absent.

This tightens access and there is deliberately no backfill: the operator scope
reaches applications.create/edit, so operator-scope holders lose the library.
Granting them endpoints.runscript on upgrade would undo the change and hand
every operator remote SYSTEM code execution, since the same grant gates the
push. A test pins that no prebuilt scope confers it.

Adds the System Scripts settings page and card. The route requires the
permission rather than admin, because the grant is meant for non-admin
operators and the sibling admin gate would lock them out of the library the
grant exists for. The settings icon map gains terminal and key; the agent-key
card had been asking for key since it shipped and silently drawing a puzzle
piece.

The library had no test coverage before this.
2026-09-09 06:37:02 -04:00

51 lines
1.9 KiB
JavaScript

/**
* System Scripts settings page.
*
* Two things are worth pinning here and nothing else is:
*
* 1. The page must NOT offer a run context. Scripts run as SYSTEM because the
* agent is a service with no user session; exposing a picker would let
* someone author a script that reads as "run as the logged-on user" and
* silently does not.
* 2. The SYSTEM warning must be present on both the list and the add/edit
* form. Someone pasting a script into the modal may never have read the
* page copy above it.
*
* Asserted against the file text, matching configurationPolicyEditable.spec.js.
*/
import { describe, it, expect } from 'vitest'
import { readFileSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
const here = dirname(fileURLToPath(import.meta.url))
const source = readFileSync(join(here, 'SystemScriptsSettings.vue'), 'utf8')
const routes = readFileSync(join(here, '..', 'routes.js'), 'utf8')
describe('SystemScriptsSettings', () => {
it('warns that scripts run as SYSTEM on the list page and in the form', () => {
const warnings = source.match(/runs? as SYSTEM/g) || []
expect(warnings.length).toBeGreaterThanOrEqual(2)
})
it('does not offer a run context picker', () => {
expect(source).not.toMatch(/runcontext/i)
})
it('does not send a runcontext in its save payload', () => {
const payload = source.slice(source.indexOf('const payload = {'))
expect(payload.slice(0, 200)).not.toMatch(/runcontext/i)
})
it('flags a script with no body as unrunnable', () => {
expect(source).toMatch(/hasbody/)
expect(source).toMatch(/cannot run/i)
})
it('is gated on the standalone script permission, not admin', () => {
const route = routes.slice(routes.indexOf("'settings/endpoints/scripts'"))
expect(route).toMatch(/requiresPermission: 'endpoints\.runscript'/)
expect(route.slice(0, 300)).not.toMatch(/requiresAdmin/)
})
})