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.
This commit is contained in:
benjamin.baker
2026-09-09 19:23:43 -04:00
parent 494b837bae
commit b49fb60c0c
19 changed files with 2207 additions and 21 deletions

View File

@@ -113,6 +113,13 @@
<span v-else class="badge badge-warning" title="A failing line does not fail the command">
Continue
</span>
<span
v-if="s.captureoutput"
class="badge badge-secondary"
title="A successful run returns its output to ShopDB"
>
Returns output
</span>
</td>
<td>
<span v-if="s.hasbody" class="badge badge-secondary">{{ s.filename }}</span>
@@ -213,6 +220,27 @@
</p>
</details>
</div>
<div class="form-group">
<label>
<input v-model="form.captureoutput" type="checkbox" />
Return output to ShopDB on success
</label>
<details class="field-help">
<summary>How this works</summary>
<p>
A failed run always sends its output back, so you can diagnose
it from the Run Scripts history without going to the machine.
A successful run does not, unless you tick this.
</p>
<p>
Leave it off for scripts whose output could contain anything
sensitive - account names, share paths, anything a Write-Host
left behind. Turn it on for scripts where seeing the output is
the only way to tell the run did real work. Either way the
full output is written to the agent log on the endpoint.
</p>
</details>
</div>
<div class="form-group">
<label>
<input v-model="form.isactive" type="checkbox" />
@@ -259,6 +287,7 @@ function emptyForm() {
scriptdescription: '',
workingdirectory: '',
errormode: 'default',
captureoutput: false,
isactive: true
}
}
@@ -293,6 +322,7 @@ function openModal(script = null) {
scriptdescription: script.scriptdescription || '',
workingdirectory: script.workingdirectory || '',
errormode: script.errormode || 'default',
captureoutput: !!script.captureoutput,
isactive: script.isactive
}
: emptyForm()
@@ -314,6 +344,7 @@ async function save() {
scriptdescription: form.value.scriptdescription,
workingdirectory: form.value.workingdirectory,
errormode: form.value.errormode,
captureoutput: form.value.captureoutput,
isactive: form.value.isactive
}
let scriptid