@phigon/observer 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -0
- package/dist/bin/guardian-runner-darwin-amd64 +0 -0
- package/dist/bin/guardian-runner-darwin-arm64 +0 -0
- package/dist/bin/guardian-runner-linux-amd64 +0 -0
- package/dist/bin/guardian-runner-linux-arm64 +0 -0
- package/dist/bin/guardian-runner-windows-amd64.exe +0 -0
- package/dist/bin/guardian-runner-windows-arm64.exe +0 -0
- package/dist/src/action-policy.d.ts +38 -0
- package/dist/src/action-policy.d.ts.map +1 -0
- package/dist/src/action-policy.js +269 -0
- package/dist/src/action-policy.js.map +1 -0
- package/dist/src/activity/adapters/claude.d.ts +3 -0
- package/dist/src/activity/adapters/claude.d.ts.map +1 -0
- package/dist/src/activity/adapters/claude.js +89 -0
- package/dist/src/activity/adapters/claude.js.map +1 -0
- package/dist/src/activity/adapters/codex.d.ts +3 -0
- package/dist/src/activity/adapters/codex.d.ts.map +1 -0
- package/dist/src/activity/adapters/codex.js +277 -0
- package/dist/src/activity/adapters/codex.js.map +1 -0
- package/dist/src/activity/adapters/copilot.d.ts +2 -0
- package/dist/src/activity/adapters/copilot.d.ts.map +1 -0
- package/dist/src/activity/adapters/copilot.js +4 -0
- package/dist/src/activity/adapters/copilot.js.map +1 -0
- package/dist/src/activity/adapters/generic.d.ts +2 -0
- package/dist/src/activity/adapters/generic.d.ts.map +1 -0
- package/dist/src/activity/adapters/generic.js +4 -0
- package/dist/src/activity/adapters/generic.js.map +1 -0
- package/dist/src/activity/adapters/hermes.d.ts +2 -0
- package/dist/src/activity/adapters/hermes.d.ts.map +1 -0
- package/dist/src/activity/adapters/hermes.js +4 -0
- package/dist/src/activity/adapters/hermes.js.map +1 -0
- package/dist/src/activity/adapters/openai-compatible.d.ts +4 -0
- package/dist/src/activity/adapters/openai-compatible.d.ts.map +1 -0
- package/dist/src/activity/adapters/openai-compatible.js +116 -0
- package/dist/src/activity/adapters/openai-compatible.js.map +1 -0
- package/dist/src/activity/adapters/openclaw.d.ts +2 -0
- package/dist/src/activity/adapters/openclaw.d.ts.map +1 -0
- package/dist/src/activity/adapters/openclaw.js +4 -0
- package/dist/src/activity/adapters/openclaw.js.map +1 -0
- package/dist/src/activity/adapters/opencode.d.ts +3 -0
- package/dist/src/activity/adapters/opencode.d.ts.map +1 -0
- package/dist/src/activity/adapters/opencode.js +137 -0
- package/dist/src/activity/adapters/opencode.js.map +1 -0
- package/dist/src/activity/bridges.d.ts +7 -0
- package/dist/src/activity/bridges.d.ts.map +1 -0
- package/dist/src/activity/bridges.js +17 -0
- package/dist/src/activity/bridges.js.map +1 -0
- package/dist/src/activity/collector.d.ts +90 -0
- package/dist/src/activity/collector.d.ts.map +1 -0
- package/dist/src/activity/collector.js +706 -0
- package/dist/src/activity/collector.js.map +1 -0
- package/dist/src/activity/index.d.ts +14 -0
- package/dist/src/activity/index.d.ts.map +1 -0
- package/dist/src/activity/index.js +14 -0
- package/dist/src/activity/index.js.map +1 -0
- package/dist/src/activity/ingest.d.ts +15 -0
- package/dist/src/activity/ingest.d.ts.map +1 -0
- package/dist/src/activity/ingest.js +103 -0
- package/dist/src/activity/ingest.js.map +1 -0
- package/dist/src/activity/native-capabilities.d.ts +14 -0
- package/dist/src/activity/native-capabilities.d.ts.map +1 -0
- package/dist/src/activity/native-capabilities.js +85 -0
- package/dist/src/activity/native-capabilities.js.map +1 -0
- package/dist/src/activity/normalize.d.ts +17 -0
- package/dist/src/activity/normalize.d.ts.map +1 -0
- package/dist/src/activity/normalize.js +416 -0
- package/dist/src/activity/normalize.js.map +1 -0
- package/dist/src/activity/opencode-sqlite-source.d.ts +19 -0
- package/dist/src/activity/opencode-sqlite-source.d.ts.map +1 -0
- package/dist/src/activity/opencode-sqlite-source.js +305 -0
- package/dist/src/activity/opencode-sqlite-source.js.map +1 -0
- package/dist/src/activity/policy.d.ts +48 -0
- package/dist/src/activity/policy.d.ts.map +1 -0
- package/dist/src/activity/policy.js +436 -0
- package/dist/src/activity/policy.js.map +1 -0
- package/dist/src/activity/record.d.ts +7 -0
- package/dist/src/activity/record.d.ts.map +1 -0
- package/dist/src/activity/record.js +61 -0
- package/dist/src/activity/record.js.map +1 -0
- package/dist/src/activity/source-reader.d.ts +46 -0
- package/dist/src/activity/source-reader.d.ts.map +1 -0
- package/dist/src/activity/source-reader.js +594 -0
- package/dist/src/activity/source-reader.js.map +1 -0
- package/dist/src/activity/types.d.ts +21 -0
- package/dist/src/activity/types.d.ts.map +1 -0
- package/dist/src/activity/types.js +2 -0
- package/dist/src/activity/types.js.map +1 -0
- package/dist/src/agent-action-executor.d.ts +21 -0
- package/dist/src/agent-action-executor.d.ts.map +1 -0
- package/dist/src/agent-action-executor.js +248 -0
- package/dist/src/agent-action-executor.js.map +1 -0
- package/dist/src/agentic-guardian.d.ts +50 -0
- package/dist/src/agentic-guardian.d.ts.map +1 -0
- package/dist/src/agentic-guardian.js +220 -0
- package/dist/src/agentic-guardian.js.map +1 -0
- package/dist/src/atomic-replace.d.ts +4 -0
- package/dist/src/atomic-replace.d.ts.map +1 -0
- package/dist/src/atomic-replace.js +18 -0
- package/dist/src/atomic-replace.js.map +1 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +1244 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +68 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +260 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/credential-store.d.ts +22 -0
- package/dist/src/credential-store.d.ts.map +1 -0
- package/dist/src/credential-store.js +91 -0
- package/dist/src/credential-store.js.map +1 -0
- package/dist/src/custom-agent-registry.d.ts +26 -0
- package/dist/src/custom-agent-registry.d.ts.map +1 -0
- package/dist/src/custom-agent-registry.js +288 -0
- package/dist/src/custom-agent-registry.js.map +1 -0
- package/dist/src/discovery/adapters/claude.d.ts +3 -0
- package/dist/src/discovery/adapters/claude.d.ts.map +1 -0
- package/dist/src/discovery/adapters/claude.js +26 -0
- package/dist/src/discovery/adapters/claude.js.map +1 -0
- package/dist/src/discovery/adapters/codex.d.ts +3 -0
- package/dist/src/discovery/adapters/codex.d.ts.map +1 -0
- package/dist/src/discovery/adapters/codex.js +23 -0
- package/dist/src/discovery/adapters/codex.js.map +1 -0
- package/dist/src/discovery/adapters/custom.d.ts +4 -0
- package/dist/src/discovery/adapters/custom.d.ts.map +1 -0
- package/dist/src/discovery/adapters/custom.js +24 -0
- package/dist/src/discovery/adapters/custom.js.map +1 -0
- package/dist/src/discovery/adapters/generic.d.ts +3 -0
- package/dist/src/discovery/adapters/generic.d.ts.map +1 -0
- package/dist/src/discovery/adapters/generic.js +126 -0
- package/dist/src/discovery/adapters/generic.js.map +1 -0
- package/dist/src/discovery/adapters/hermes.d.ts +3 -0
- package/dist/src/discovery/adapters/hermes.d.ts.map +1 -0
- package/dist/src/discovery/adapters/hermes.js +22 -0
- package/dist/src/discovery/adapters/hermes.js.map +1 -0
- package/dist/src/discovery/adapters/openclaw.d.ts +3 -0
- package/dist/src/discovery/adapters/openclaw.d.ts.map +1 -0
- package/dist/src/discovery/adapters/openclaw.js +22 -0
- package/dist/src/discovery/adapters/openclaw.js.map +1 -0
- package/dist/src/discovery/engine.d.ts +17 -0
- package/dist/src/discovery/engine.d.ts.map +1 -0
- package/dist/src/discovery/engine.js +164 -0
- package/dist/src/discovery/engine.js.map +1 -0
- package/dist/src/discovery/evidence.d.ts +29 -0
- package/dist/src/discovery/evidence.d.ts.map +1 -0
- package/dist/src/discovery/evidence.js +2 -0
- package/dist/src/discovery/evidence.js.map +1 -0
- package/dist/src/discovery/index.d.ts +11 -0
- package/dist/src/discovery/index.d.ts.map +1 -0
- package/dist/src/discovery/index.js +11 -0
- package/dist/src/discovery/index.js.map +1 -0
- package/dist/src/discovery/known-agent.d.ts +17 -0
- package/dist/src/discovery/known-agent.d.ts.map +1 -0
- package/dist/src/discovery/known-agent.js +193 -0
- package/dist/src/discovery/known-agent.js.map +1 -0
- package/dist/src/discovery/process-identity.d.ts +19 -0
- package/dist/src/discovery/process-identity.d.ts.map +1 -0
- package/dist/src/discovery/process-identity.js +73 -0
- package/dist/src/discovery/process-identity.js.map +1 -0
- package/dist/src/discovery/system-evidence.d.ts +9 -0
- package/dist/src/discovery/system-evidence.d.ts.map +1 -0
- package/dist/src/discovery/system-evidence.js +270 -0
- package/dist/src/discovery/system-evidence.js.map +1 -0
- package/dist/src/discovery/types.d.ts +21 -0
- package/dist/src/discovery/types.d.ts.map +1 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/discovery/types.js.map +1 -0
- package/dist/src/event-queue.d.ts +32 -0
- package/dist/src/event-queue.d.ts.map +1 -0
- package/dist/src/event-queue.js +125 -0
- package/dist/src/event-queue.js.map +1 -0
- package/dist/src/event-spool.d.ts +19 -0
- package/dist/src/event-spool.d.ts.map +1 -0
- package/dist/src/event-spool.js +350 -0
- package/dist/src/event-spool.js.map +1 -0
- package/dist/src/generated/phigon/v1/action_pb.d.ts +223 -0
- package/dist/src/generated/phigon/v1/action_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/action_pb.js +96 -0
- package/dist/src/generated/phigon/v1/action_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/common_pb.d.ts +255 -0
- package/dist/src/generated/phigon/v1/common_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/common_pb.js +242 -0
- package/dist/src/generated/phigon/v1/common_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/history_sync_pb.d.ts +119 -0
- package/dist/src/generated/phigon/v1/history_sync_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/history_sync_pb.js +46 -0
- package/dist/src/generated/phigon/v1/history_sync_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/identity_pb.d.ts +235 -0
- package/dist/src/generated/phigon/v1/identity_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/identity_pb.js +31 -0
- package/dist/src/generated/phigon/v1/identity_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/observer_service_pb.d.ts +362 -0
- package/dist/src/generated/phigon/v1/observer_service_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/observer_service_pb.js +120 -0
- package/dist/src/generated/phigon/v1/observer_service_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/policy_pb.d.ts +456 -0
- package/dist/src/generated/phigon/v1/policy_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/policy_pb.js +161 -0
- package/dist/src/generated/phigon/v1/policy_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/supervision_pb.d.ts +849 -0
- package/dist/src/generated/phigon/v1/supervision_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/supervision_pb.js +384 -0
- package/dist/src/generated/phigon/v1/supervision_pb.js.map +1 -0
- package/dist/src/generated/phigon/v1/telemetry_pb.d.ts +761 -0
- package/dist/src/generated/phigon/v1/telemetry_pb.d.ts.map +1 -0
- package/dist/src/generated/phigon/v1/telemetry_pb.js +309 -0
- package/dist/src/generated/phigon/v1/telemetry_pb.js.map +1 -0
- package/dist/src/guardian-containment.d.ts +30 -0
- package/dist/src/guardian-containment.d.ts.map +1 -0
- package/dist/src/guardian-containment.js +113 -0
- package/dist/src/guardian-containment.js.map +1 -0
- package/dist/src/guardian-decision-journal.d.ts +29 -0
- package/dist/src/guardian-decision-journal.d.ts.map +1 -0
- package/dist/src/guardian-decision-journal.js +55 -0
- package/dist/src/guardian-decision-journal.js.map +1 -0
- package/dist/src/guardian-execution-manifest.d.ts +29 -0
- package/dist/src/guardian-execution-manifest.d.ts.map +1 -0
- package/dist/src/guardian-execution-manifest.js +95 -0
- package/dist/src/guardian-execution-manifest.js.map +1 -0
- package/dist/src/guardian-hosted-client.d.ts +10 -0
- package/dist/src/guardian-hosted-client.d.ts.map +1 -0
- package/dist/src/guardian-hosted-client.js +45 -0
- package/dist/src/guardian-hosted-client.js.map +1 -0
- package/dist/src/guardian-managed-exec.d.ts +21 -0
- package/dist/src/guardian-managed-exec.d.ts.map +1 -0
- package/dist/src/guardian-managed-exec.js +249 -0
- package/dist/src/guardian-managed-exec.js.map +1 -0
- package/dist/src/guardian-model-bridge.d.ts +13 -0
- package/dist/src/guardian-model-bridge.d.ts.map +1 -0
- package/dist/src/guardian-model-bridge.js +87 -0
- package/dist/src/guardian-model-bridge.js.map +1 -0
- package/dist/src/guardian-native.d.ts +3 -0
- package/dist/src/guardian-native.d.ts.map +1 -0
- package/dist/src/guardian-native.js +13 -0
- package/dist/src/guardian-native.js.map +1 -0
- package/dist/src/guardian-process-monitor.d.ts +20 -0
- package/dist/src/guardian-process-monitor.d.ts.map +1 -0
- package/dist/src/guardian-process-monitor.js +98 -0
- package/dist/src/guardian-process-monitor.js.map +1 -0
- package/dist/src/health.d.ts +35 -0
- package/dist/src/health.d.ts.map +1 -0
- package/dist/src/health.js +41 -0
- package/dist/src/health.js.map +1 -0
- package/dist/src/historical-identity-store.d.ts +20 -0
- package/dist/src/historical-identity-store.d.ts.map +1 -0
- package/dist/src/historical-identity-store.js +391 -0
- package/dist/src/historical-identity-store.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +14 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/installation.d.ts +2 -0
- package/dist/src/installation.d.ts.map +1 -0
- package/dist/src/installation.js +44 -0
- package/dist/src/installation.js.map +1 -0
- package/dist/src/machine-command-executor.d.ts +28 -0
- package/dist/src/machine-command-executor.d.ts.map +1 -0
- package/dist/src/machine-command-executor.js +166 -0
- package/dist/src/machine-command-executor.js.map +1 -0
- package/dist/src/pair.d.ts +21 -0
- package/dist/src/pair.d.ts.map +1 -0
- package/dist/src/pair.js +125 -0
- package/dist/src/pair.js.map +1 -0
- package/dist/src/remove-machine.d.ts +15 -0
- package/dist/src/remove-machine.d.ts.map +1 -0
- package/dist/src/remove-machine.js +77 -0
- package/dist/src/remove-machine.js.map +1 -0
- package/dist/src/stream-client.d.ts +42 -0
- package/dist/src/stream-client.d.ts.map +1 -0
- package/dist/src/stream-client.js +867 -0
- package/dist/src/stream-client.js.map +1 -0
- package/dist/src/stream-state.d.ts +13 -0
- package/dist/src/stream-state.d.ts.map +1 -0
- package/dist/src/stream-state.js +128 -0
- package/dist/src/stream-state.js.map +1 -0
- package/dist/src/windows-process.d.ts +7 -0
- package/dist/src/windows-process.d.ts.map +1 -0
- package/dist/src/windows-process.js +17 -0
- package/dist/src/windows-process.js.map +1 -0
- package/package.json +78 -0
- package/scripts/install.mjs +308 -0
- package/scripts/managed-service.mjs +132 -0
- package/scripts/verify-guardian-release.mjs +42 -0
- package/scripts/windows-app-control.mjs +117 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Produces reviewable deployment files. It never modifies services or firewalls
|
|
3
|
+
// on the developer's machine. Run the generated installer through company MDM.
|
|
4
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { join, posix, resolve, win32 } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
export function serviceFiles(config) {
|
|
9
|
+
const { platform, node, observer, dataDirectory, controlPlane, grpc, serviceUser = "phigon" } = config;
|
|
10
|
+
if (!["windows", "linux", "darwin"].includes(platform)) throw new Error("Unsupported managed platform.");
|
|
11
|
+
const absolute = platform === "windows" ? (value) => /^[A-Za-z]:[\\/]/u.test(value) : posix.isAbsolute;
|
|
12
|
+
for (const path of [node, observer, dataDirectory]) {
|
|
13
|
+
if (typeof path !== "string" || !absolute(path) || /[\u0000\r\n%]/u.test(path)) throw new Error("Managed deployment requires absolute paths without control characters or expansion markers.");
|
|
14
|
+
if (platform === "windows" && /[<>"|?*']/u.test(path)) throw new Error("Windows deployment paths contain unsupported characters.");
|
|
15
|
+
}
|
|
16
|
+
const targetPaths = platform === "windows" ? win32 : posix;
|
|
17
|
+
if (targetPaths.normalize(dataDirectory) === targetPaths.parse(dataDirectory).root) throw new Error("Guardian data requires its own directory, never a filesystem root.");
|
|
18
|
+
for (const endpoint of [controlPlane, grpc]) {
|
|
19
|
+
if (typeof endpoint !== "string" || /[\u0000\r\n]/u.test(endpoint)) throw new Error("Invalid managed endpoint.");
|
|
20
|
+
const url = new URL(endpoint);
|
|
21
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("Managed endpoints require HTTPS without embedded credentials.");
|
|
22
|
+
}
|
|
23
|
+
if (!/^[a-z_][a-z0-9_-]{0,31}$/u.test(serviceUser)) throw new Error("Invalid service account.");
|
|
24
|
+
const ps = (value) => "'" + value.replaceAll("'", "''") + "'";
|
|
25
|
+
if (platform === "windows") {
|
|
26
|
+
return { "install-guardian.ps1": `# Run as administrator through your company's device management.
|
|
27
|
+
$ErrorActionPreference = 'Stop'
|
|
28
|
+
$taskNode = ${ps(node)}
|
|
29
|
+
$taskObserver = ${ps(observer)}
|
|
30
|
+
$taskData = ${ps(dataDirectory)}
|
|
31
|
+
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
32
|
+
if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Administrator deployment is required.' }
|
|
33
|
+
if (-not (Test-Path -LiteralPath $taskNode -PathType Leaf) -or -not (Test-Path -LiteralPath $taskObserver -PathType Leaf)) { throw 'Install and verify the release before registering Guardian.' }
|
|
34
|
+
$taskAncestor = [IO.Path]::GetFullPath($taskData)
|
|
35
|
+
while ($taskAncestor) {
|
|
36
|
+
if ((Test-Path -LiteralPath $taskAncestor) -and ((Get-Item -LiteralPath $taskAncestor -Force).Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'Guardian data path must not traverse reparse points.' }
|
|
37
|
+
$taskAncestor = Split-Path -Path $taskAncestor -Parent
|
|
38
|
+
}
|
|
39
|
+
[IO.Directory]::CreateDirectory($taskData) | Out-Null
|
|
40
|
+
& "$env:SystemRoot\\System32\\icacls.exe" $taskData /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' | Out-Null
|
|
41
|
+
if ($LASTEXITCODE -ne 0) { throw 'Guardian data ACL could not be secured.' }
|
|
42
|
+
# Existing explicit grants are not removed by icacls /grant:r. Reject an
|
|
43
|
+
# insecure enrollment directory instead of starting with exposed credentials.
|
|
44
|
+
$taskPaths = @((Get-Item -LiteralPath $taskData -Force)) + @(Get-ChildItem -LiteralPath $taskData -Force -Recurse)
|
|
45
|
+
foreach ($taskPath in $taskPaths) {
|
|
46
|
+
if ($taskPath.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Guardian data must not contain reparse points.' }
|
|
47
|
+
$taskAcl = Get-Acl -LiteralPath $taskPath.FullName
|
|
48
|
+
foreach ($taskRule in $taskAcl.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])) {
|
|
49
|
+
if ($taskRule.AccessControlType -eq [Security.AccessControl.AccessControlType]::Allow -and $taskRule.IdentityReference.Value -notin @('S-1-5-18', 'S-1-5-32-544')) { throw 'Remove non-administrator data access before starting Guardian.' }
|
|
50
|
+
}
|
|
51
|
+
if ($taskAcl.GetOwner([Security.Principal.SecurityIdentifier]).Value -notin @('S-1-5-18', 'S-1-5-32-544')) { throw 'Guardian data ownership must belong to SYSTEM or Administrators.' }
|
|
52
|
+
}
|
|
53
|
+
# A SYSTEM startup task supervises process review. Per-user collection paths and
|
|
54
|
+
# machine pairing must be provisioned separately into the protected data path.
|
|
55
|
+
$taskArguments = '"' + $taskObserver + '" run'
|
|
56
|
+
$taskAction = New-ScheduledTaskAction -Execute $taskNode -Argument $taskArguments
|
|
57
|
+
$taskTrigger = New-ScheduledTaskTrigger -AtStartup
|
|
58
|
+
$taskSettings = New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) -StartWhenAvailable -MultipleInstances IgnoreNew
|
|
59
|
+
$taskPrincipal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
|
60
|
+
# Environment file is used by Node, rather than machine-wide environment changes.
|
|
61
|
+
$taskEnv = Join-Path $taskData 'service.env'
|
|
62
|
+
@'
|
|
63
|
+
PHIGON_DATA_DIR='${dataDirectory}'
|
|
64
|
+
PHIGON_CONTROL_PLANE_URL=${controlPlane}
|
|
65
|
+
PHIGON_GRPC_URL=${grpc}
|
|
66
|
+
'@ | Set-Content -LiteralPath $taskEnv -Encoding utf8
|
|
67
|
+
$taskAction = New-ScheduledTaskAction -Execute $taskNode -Argument ('--env-file="' + $taskEnv + '" ' + $taskArguments)
|
|
68
|
+
Register-ScheduledTask -TaskName 'PhigonGuardian' -Action $taskAction -Trigger $taskTrigger -Settings $taskSettings -Principal $taskPrincipal -Force | Out-Null
|
|
69
|
+
Start-ScheduledTask -TaskName 'PhigonGuardian'
|
|
70
|
+
` };
|
|
71
|
+
}
|
|
72
|
+
const xml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
73
|
+
if (platform === "darwin") return { "com.phigon.guardian.plist": `<?xml version="1.0" encoding="UTF-8"?>
|
|
74
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
75
|
+
<plist version="1.0"><dict>
|
|
76
|
+
<key>Label</key><string>com.phigon.guardian</string>
|
|
77
|
+
<key>ProgramArguments</key><array><string>${xml(node)}</string><string>${xml(observer)}</string><string>run</string></array>
|
|
78
|
+
<key>UserName</key><string>${xml(serviceUser)}</string>
|
|
79
|
+
<key>EnvironmentVariables</key><dict><key>PHIGON_DATA_DIR</key><string>${xml(dataDirectory)}</string><key>PHIGON_CONTROL_PLANE_URL</key><string>${xml(controlPlane)}</string><key>PHIGON_GRPC_URL</key><string>${xml(grpc)}</string></dict>
|
|
80
|
+
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>ThrottleInterval</key><integer>10</integer>
|
|
81
|
+
<key>ProcessType</key><string>Background</string><key>Umask</key><integer>63</integer>
|
|
82
|
+
</dict></plist>
|
|
83
|
+
` };
|
|
84
|
+
const unit = (value) => '"' + value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%") + '"';
|
|
85
|
+
const executableArgument = (value) => unit(value.replaceAll("$", "$$"));
|
|
86
|
+
return { "phigon-guardian.service": `[Unit]
|
|
87
|
+
Description=Phigon Guardian enterprise observer
|
|
88
|
+
After=network-online.target
|
|
89
|
+
Wants=network-online.target
|
|
90
|
+
StartLimitIntervalSec=0
|
|
91
|
+
|
|
92
|
+
[Service]
|
|
93
|
+
Type=simple
|
|
94
|
+
User=${serviceUser}
|
|
95
|
+
Group=${serviceUser}
|
|
96
|
+
ExecStart=${executableArgument(node)} ${executableArgument(observer)} run
|
|
97
|
+
Environment=${unit("PHIGON_DATA_DIR=" + dataDirectory)}
|
|
98
|
+
Environment=${unit("PHIGON_CONTROL_PLANE_URL=" + controlPlane)}
|
|
99
|
+
Environment=${unit("PHIGON_GRPC_URL=" + grpc)}
|
|
100
|
+
Restart=always
|
|
101
|
+
RestartSec=5
|
|
102
|
+
UMask=0077
|
|
103
|
+
NoNewPrivileges=yes
|
|
104
|
+
AmbientCapabilities=CAP_KILL CAP_DAC_READ_SEARCH
|
|
105
|
+
CapabilityBoundingSet=CAP_KILL CAP_DAC_READ_SEARCH
|
|
106
|
+
ProtectSystem=strict
|
|
107
|
+
ProtectKernelTunables=yes
|
|
108
|
+
ProtectKernelModules=yes
|
|
109
|
+
ProtectControlGroups=yes
|
|
110
|
+
RestrictSUIDSGID=yes
|
|
111
|
+
LockPersonality=yes
|
|
112
|
+
ReadWritePaths=${unit(dataDirectory)}
|
|
113
|
+
KillMode=control-group
|
|
114
|
+
TimeoutStopSec=15
|
|
115
|
+
|
|
116
|
+
[Install]
|
|
117
|
+
WantedBy=multi-user.target
|
|
118
|
+
` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
122
|
+
const [configPath, output] = process.argv.slice(2);
|
|
123
|
+
if (!configPath || !output) throw new Error("Usage: node managed-service.mjs <deployment.json> <output-directory>");
|
|
124
|
+
const files = serviceFiles(JSON.parse(await readFile(configPath, "utf8")));
|
|
125
|
+
const directory = resolve(output); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
126
|
+
for (const [name, content] of Object.entries(files)) {
|
|
127
|
+
const destination = join(directory, name);
|
|
128
|
+
try { await access(destination); throw new Error(`Deployment file already exists: ${name}`); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
129
|
+
await writeFile(destination, content, { flag: "wx", mode: 0o600 });
|
|
130
|
+
process.stdout.write(`${destination}\n`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function verifyNativeHeader(bytes, platform, architecture) {
|
|
6
|
+
if (bytes.length < 256) throw new Error("Guardian executable is incomplete.");
|
|
7
|
+
const arm = architecture === "arm64";
|
|
8
|
+
if (platform === "windows") {
|
|
9
|
+
const pe = bytes.readUInt32LE(0x3c);
|
|
10
|
+
if (bytes.toString("ascii", 0, 2) !== "MZ" || pe > bytes.length - 6 || bytes.readUInt32LE(pe) !== 0x4550 || bytes.readUInt16LE(pe + 4) !== (arm ? 0xaa64 : 0x8664)) {
|
|
11
|
+
throw new Error("Guardian Windows executable has the wrong format or architecture.");
|
|
12
|
+
}
|
|
13
|
+
} else if (platform === "linux") {
|
|
14
|
+
if (!bytes.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])) || bytes[4] !== 2 || bytes[5] !== 1 || bytes.readUInt16LE(18) !== (arm ? 183 : 62)) {
|
|
15
|
+
throw new Error("Guardian Linux executable has the wrong format or architecture.");
|
|
16
|
+
}
|
|
17
|
+
} else if (platform === "darwin") {
|
|
18
|
+
if (bytes.readUInt32LE(0) !== 0xfeedfacf || bytes.readUInt32LE(4) !== (arm ? 0x100000c : 0x1000007)) {
|
|
19
|
+
throw new Error("Guardian macOS executable has the wrong format or architecture.");
|
|
20
|
+
}
|
|
21
|
+
} else throw new Error("Unknown Guardian platform.");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function verifyGuardianRelease() {
|
|
25
|
+
for (const platform of ["windows", "linux", "darwin"]) {
|
|
26
|
+
for (const architecture of ["amd64", "arm64"]) {
|
|
27
|
+
const name = `guardian-runner-${platform}-${architecture}${platform === "windows" ? ".exe" : ""}`;
|
|
28
|
+
const file = new URL(`../dist/bin/${name}`, import.meta.url);
|
|
29
|
+
const stat = await lstat(file);
|
|
30
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`Release requires a regular native executable: ${name}`);
|
|
31
|
+
verifyNativeHeader(await readFile(file), platform, architecture);
|
|
32
|
+
if (platform !== "windows" && (stat.mode & 0o111) !== 0o111) {
|
|
33
|
+
throw new Error(`${name} lacks Unix executable permissions. Build and pack the portable release on Linux or macOS using the release workflow.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
40
|
+
await verifyGuardianRelease();
|
|
41
|
+
process.stdout.write("Guardian release verified: six native executables with correct architectures and permissions.\n");
|
|
42
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Generates a reviewable AppLocker deployment package. Rendering never changes
|
|
2
|
+
// the current machine's policy, services, registry, or firewall.
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
export function applicationControlFiles(config) {
|
|
8
|
+
if (!config || !["audit", "enforce"].includes(config.mode) || !/^S-1-\d+(?:-\d+)+$/u.test(config.user_or_group_sid ?? "") ||
|
|
9
|
+
!Array.isArray(config.applications) || config.applications.length < 1 || config.applications.length > 64) throw new Error("Application control requires an explicit mode, account/group SID and 1–64 applications.");
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
for (const app of config.applications) {
|
|
12
|
+
if (!app || typeof app.path !== "string" || !/^[A-Za-z]:[\\/].+\.exe$/iu.test(app.path) || /[\u0000-\u001f<>"|?*%\[\]]/u.test(app.path) || /(?:^|[\\/])\.{1,2}(?:[\\/]|$)/u.test(app.path) ||
|
|
13
|
+
!["hash", "publisher"].includes(app.rule_type) || seen.has(app.path.toLowerCase())) throw new Error("Applications require unique absolute .exe paths and hash/publisher rule types.");
|
|
14
|
+
seen.add(app.path.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
const settings = JSON.stringify({ mode: config.mode, user_or_group_sid: config.user_or_group_sid, applications: config.applications }, null, 2);
|
|
17
|
+
return {
|
|
18
|
+
"applications.json": settings + "\n",
|
|
19
|
+
"Build-GuardianAppControl.ps1": `# Windows PowerShell with the AppLocker module. Generates files only.
|
|
20
|
+
$ErrorActionPreference = 'Stop'
|
|
21
|
+
Import-Module AppLocker -ErrorAction Stop
|
|
22
|
+
$configuration = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'applications.json') -Raw | ConvertFrom-Json
|
|
23
|
+
$previousPath = Join-Path $PSScriptRoot 'previous-policy.xml'
|
|
24
|
+
$policyPath = Join-Path $PSScriptRoot 'guardian-policy.xml'
|
|
25
|
+
if ((Test-Path -LiteralPath $previousPath) -or (Test-Path -LiteralPath $policyPath)) { throw 'Use a fresh deployment directory; existing review artifacts will not be overwritten.' }
|
|
26
|
+
$previous = Get-AppLockerPolicy -Local -Xml
|
|
27
|
+
[xml]$policy = $previous
|
|
28
|
+
$mode = if ($configuration.mode -eq 'enforce') { 'Enabled' } else { 'AuditOnly' }
|
|
29
|
+
$collection = $policy.SelectSingleNode('/AppLockerPolicy/RuleCollection[@Type="Exe"]')
|
|
30
|
+
if (-not $collection) {
|
|
31
|
+
$collection = $policy.CreateElement('RuleCollection')
|
|
32
|
+
$collection.SetAttribute('Type','Exe')
|
|
33
|
+
$policy.DocumentElement.AppendChild($collection) | Out-Null
|
|
34
|
+
}
|
|
35
|
+
$existingRules = @($collection.SelectNodes('FilePathRule|FileHashRule|FilePublisherRule'))
|
|
36
|
+
if ($existingRules.Count -gt 0 -and $collection.EnforcementMode -ne $mode) { throw 'Existing EXE rules use another enforcement mode. Review that policy through company device management before combining it.' }
|
|
37
|
+
$collection.SetAttribute('EnforcementMode',$mode)
|
|
38
|
+
if ($existingRules.Count -eq 0) {
|
|
39
|
+
# A deny-only deployment must not accidentally become a machine allowlist.
|
|
40
|
+
$allow = $policy.CreateElement('FilePathRule')
|
|
41
|
+
$allow.SetAttribute('Id',[guid]::NewGuid().ToString()); $allow.SetAttribute('Name','Preserve other application access')
|
|
42
|
+
$allow.SetAttribute('Description','Initial default for an otherwise empty EXE collection')
|
|
43
|
+
$allow.SetAttribute('UserOrGroupSid','S-1-1-0'); $allow.SetAttribute('Action','Allow')
|
|
44
|
+
$conditions = $policy.CreateElement('Conditions'); $path = $policy.CreateElement('FilePathCondition'); $path.SetAttribute('Path','*')
|
|
45
|
+
$conditions.AppendChild($path) | Out-Null; $allow.AppendChild($conditions) | Out-Null; $collection.AppendChild($allow) | Out-Null
|
|
46
|
+
}
|
|
47
|
+
foreach ($app in $configuration.applications) {
|
|
48
|
+
if (-not (Test-Path -LiteralPath $app.path -PathType Leaf)) { throw 'A selected application is missing from this reference machine.' }
|
|
49
|
+
$information = Get-AppLockerFileInformation -Path $app.path
|
|
50
|
+
[xml]$generated = New-AppLockerPolicy -FileInformation $information -RuleType $app.rule_type -User $configuration.user_or_group_sid -Xml
|
|
51
|
+
$rules = @($generated.SelectNodes('/AppLockerPolicy/RuleCollection[@Type="Exe"]/FileHashRule|/AppLockerPolicy/RuleCollection[@Type="Exe"]/FilePublisherRule'))
|
|
52
|
+
if ($rules.Count -eq 0) { throw 'No specific application rule could be generated. Publisher rules require signed application identity.' }
|
|
53
|
+
foreach ($rule in $rules) {
|
|
54
|
+
if (($app.rule_type -eq 'hash' -and $rule.LocalName -ne 'FileHashRule') -or ($app.rule_type -eq 'publisher' -and $rule.LocalName -ne 'FilePublisherRule')) { throw 'Generated application identity does not match the requested rule type.' }
|
|
55
|
+
if ($app.rule_type -eq 'publisher') {
|
|
56
|
+
$identity = $rule.Conditions.FilePublisherCondition
|
|
57
|
+
if (-not $identity) { throw 'Publisher identity is missing.' }
|
|
58
|
+
foreach ($part in @($identity.PublisherName, $identity.ProductName, $identity.BinaryName)) {
|
|
59
|
+
if ([string]::IsNullOrWhiteSpace($part) -or $part.Contains('*') -or $part.Contains('?')) { throw 'Publisher identity must identify one application, not an entire software publisher.' }
|
|
60
|
+
}
|
|
61
|
+
$identity.BinaryVersionRange.SetAttribute('LowSection','0.0.0.0'); $identity.BinaryVersionRange.SetAttribute('HighSection','*')
|
|
62
|
+
}
|
|
63
|
+
$rule.SetAttribute('Action','Deny'); $rule.SetAttribute('Name','Phigon Guardian: application prohibited')
|
|
64
|
+
$collection.AppendChild($policy.ImportNode($rule,$true)) | Out-Null
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
$previous | Set-Content -LiteralPath $previousPath -Encoding UTF8
|
|
68
|
+
$policy.Save($policyPath)
|
|
69
|
+
Write-Output 'Review guardian-policy.xml and previous-policy.xml before deploying. No OS policy has been applied.'
|
|
70
|
+
`,
|
|
71
|
+
"Apply-GuardianAppControl.ps1": `# Deploy the reviewed package as administrator using company device management.
|
|
72
|
+
$ErrorActionPreference = 'Stop'
|
|
73
|
+
Import-Module AppLocker -ErrorAction Stop
|
|
74
|
+
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
75
|
+
if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Administrator deployment is required.' }
|
|
76
|
+
[xml]$expected = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'previous-policy.xml') -Raw
|
|
77
|
+
[xml]$current = Get-AppLockerPolicy -Local -Xml
|
|
78
|
+
if ($current.OuterXml -ne $expected.OuterXml) { throw 'Local policy changed after review. Rebuild against the current policy; no changes applied.' }
|
|
79
|
+
Set-Service -Name AppIDSvc -StartupType Automatic
|
|
80
|
+
Start-Service -Name AppIDSvc
|
|
81
|
+
Set-AppLockerPolicy -XmlPolicy (Join-Path $PSScriptRoot 'guardian-policy.xml')
|
|
82
|
+
& (Join-Path $PSScriptRoot 'Verify-GuardianAppControl.ps1')
|
|
83
|
+
`,
|
|
84
|
+
"Verify-GuardianAppControl.ps1": `param([string]$UserSid)
|
|
85
|
+
$ErrorActionPreference = 'Stop'
|
|
86
|
+
Import-Module AppLocker -ErrorAction Stop
|
|
87
|
+
$configuration = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'applications.json') -Raw | ConvertFrom-Json
|
|
88
|
+
if ([string]::IsNullOrWhiteSpace($UserSid)) { $UserSid = $configuration.user_or_group_sid }
|
|
89
|
+
if ($UserSid -notmatch '^S-1-\\d+(-\\d+)+$') { throw 'Verification requires an account SID.' }
|
|
90
|
+
$effective = Get-AppLockerPolicy -Effective
|
|
91
|
+
[xml]$xml = Get-AppLockerPolicy -Effective -Xml
|
|
92
|
+
$collection = $xml.SelectSingleNode('/AppLockerPolicy/RuleCollection[@Type="Exe"]')
|
|
93
|
+
$decisions = @($effective | Test-AppLockerPolicy -Path @($configuration.applications | ForEach-Object { $_.path }) -User $UserSid)
|
|
94
|
+
$denied = @($decisions | Where-Object { $_.PolicyDecision -eq 'Denied' -or $_.PolicyDecision -eq 'DeniedByDefault' })
|
|
95
|
+
$verified = $collection.EnforcementMode -eq 'Enabled' -and (Get-Service -Name AppIDSvc).Status -eq 'Running' -and $denied.Count -eq $configuration.applications.Count
|
|
96
|
+
@{ evidence='effective_os_policy'; policy_verified=$verified; tested_identity=$UserSid; mode=$collection.EnforcementMode; checked_applications=$decisions.Count; runtime_test_required=$true } | ConvertTo-Json
|
|
97
|
+
if ($configuration.mode -eq 'enforce' -and -not $verified) { throw 'Effective enforcement does not match this deployment. Check domain/MDM policy and OS support.' }
|
|
98
|
+
`,
|
|
99
|
+
"Rollback-GuardianAppControl.ps1": `$ErrorActionPreference = 'Stop'
|
|
100
|
+
Import-Module AppLocker -ErrorAction Stop
|
|
101
|
+
[xml]$deployed = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'guardian-policy.xml') -Raw
|
|
102
|
+
[xml]$current = Get-AppLockerPolicy -Local -Xml
|
|
103
|
+
if ($current.OuterXml -ne $deployed.OuterXml) { throw 'Local policy changed after deployment. Automatic rollback would overwrite other administrator changes.' }
|
|
104
|
+
Set-AppLockerPolicy -XmlPolicy (Join-Path $PSScriptRoot 'previous-policy.xml')
|
|
105
|
+
Write-Output 'Previous local application policy restored. Application Identity service remains available for other company policies.'
|
|
106
|
+
`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
111
|
+
const [configPath, output] = process.argv.slice(2);
|
|
112
|
+
if (!configPath || !output) throw new Error("Usage: node windows-app-control.mjs <applications.json> <output-directory>");
|
|
113
|
+
const files = applicationControlFiles(JSON.parse(await readFile(configPath, "utf8")));
|
|
114
|
+
const directory = resolve(output); await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
115
|
+
for (const [name, content] of Object.entries(files)) await writeFile(join(directory, name), content, { flag: "wx", mode: 0o600 });
|
|
116
|
+
process.stdout.write(`Generated ${Object.keys(files).length} application-control artifacts in ${directory}.\n`);
|
|
117
|
+
}
|