@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.
Files changed (284) hide show
  1. package/README.md +303 -0
  2. package/dist/bin/guardian-runner-darwin-amd64 +0 -0
  3. package/dist/bin/guardian-runner-darwin-arm64 +0 -0
  4. package/dist/bin/guardian-runner-linux-amd64 +0 -0
  5. package/dist/bin/guardian-runner-linux-arm64 +0 -0
  6. package/dist/bin/guardian-runner-windows-amd64.exe +0 -0
  7. package/dist/bin/guardian-runner-windows-arm64.exe +0 -0
  8. package/dist/src/action-policy.d.ts +38 -0
  9. package/dist/src/action-policy.d.ts.map +1 -0
  10. package/dist/src/action-policy.js +269 -0
  11. package/dist/src/action-policy.js.map +1 -0
  12. package/dist/src/activity/adapters/claude.d.ts +3 -0
  13. package/dist/src/activity/adapters/claude.d.ts.map +1 -0
  14. package/dist/src/activity/adapters/claude.js +89 -0
  15. package/dist/src/activity/adapters/claude.js.map +1 -0
  16. package/dist/src/activity/adapters/codex.d.ts +3 -0
  17. package/dist/src/activity/adapters/codex.d.ts.map +1 -0
  18. package/dist/src/activity/adapters/codex.js +277 -0
  19. package/dist/src/activity/adapters/codex.js.map +1 -0
  20. package/dist/src/activity/adapters/copilot.d.ts +2 -0
  21. package/dist/src/activity/adapters/copilot.d.ts.map +1 -0
  22. package/dist/src/activity/adapters/copilot.js +4 -0
  23. package/dist/src/activity/adapters/copilot.js.map +1 -0
  24. package/dist/src/activity/adapters/generic.d.ts +2 -0
  25. package/dist/src/activity/adapters/generic.d.ts.map +1 -0
  26. package/dist/src/activity/adapters/generic.js +4 -0
  27. package/dist/src/activity/adapters/generic.js.map +1 -0
  28. package/dist/src/activity/adapters/hermes.d.ts +2 -0
  29. package/dist/src/activity/adapters/hermes.d.ts.map +1 -0
  30. package/dist/src/activity/adapters/hermes.js +4 -0
  31. package/dist/src/activity/adapters/hermes.js.map +1 -0
  32. package/dist/src/activity/adapters/openai-compatible.d.ts +4 -0
  33. package/dist/src/activity/adapters/openai-compatible.d.ts.map +1 -0
  34. package/dist/src/activity/adapters/openai-compatible.js +116 -0
  35. package/dist/src/activity/adapters/openai-compatible.js.map +1 -0
  36. package/dist/src/activity/adapters/openclaw.d.ts +2 -0
  37. package/dist/src/activity/adapters/openclaw.d.ts.map +1 -0
  38. package/dist/src/activity/adapters/openclaw.js +4 -0
  39. package/dist/src/activity/adapters/openclaw.js.map +1 -0
  40. package/dist/src/activity/adapters/opencode.d.ts +3 -0
  41. package/dist/src/activity/adapters/opencode.d.ts.map +1 -0
  42. package/dist/src/activity/adapters/opencode.js +137 -0
  43. package/dist/src/activity/adapters/opencode.js.map +1 -0
  44. package/dist/src/activity/bridges.d.ts +7 -0
  45. package/dist/src/activity/bridges.d.ts.map +1 -0
  46. package/dist/src/activity/bridges.js +17 -0
  47. package/dist/src/activity/bridges.js.map +1 -0
  48. package/dist/src/activity/collector.d.ts +90 -0
  49. package/dist/src/activity/collector.d.ts.map +1 -0
  50. package/dist/src/activity/collector.js +706 -0
  51. package/dist/src/activity/collector.js.map +1 -0
  52. package/dist/src/activity/index.d.ts +14 -0
  53. package/dist/src/activity/index.d.ts.map +1 -0
  54. package/dist/src/activity/index.js +14 -0
  55. package/dist/src/activity/index.js.map +1 -0
  56. package/dist/src/activity/ingest.d.ts +15 -0
  57. package/dist/src/activity/ingest.d.ts.map +1 -0
  58. package/dist/src/activity/ingest.js +103 -0
  59. package/dist/src/activity/ingest.js.map +1 -0
  60. package/dist/src/activity/native-capabilities.d.ts +14 -0
  61. package/dist/src/activity/native-capabilities.d.ts.map +1 -0
  62. package/dist/src/activity/native-capabilities.js +85 -0
  63. package/dist/src/activity/native-capabilities.js.map +1 -0
  64. package/dist/src/activity/normalize.d.ts +17 -0
  65. package/dist/src/activity/normalize.d.ts.map +1 -0
  66. package/dist/src/activity/normalize.js +416 -0
  67. package/dist/src/activity/normalize.js.map +1 -0
  68. package/dist/src/activity/opencode-sqlite-source.d.ts +19 -0
  69. package/dist/src/activity/opencode-sqlite-source.d.ts.map +1 -0
  70. package/dist/src/activity/opencode-sqlite-source.js +305 -0
  71. package/dist/src/activity/opencode-sqlite-source.js.map +1 -0
  72. package/dist/src/activity/policy.d.ts +48 -0
  73. package/dist/src/activity/policy.d.ts.map +1 -0
  74. package/dist/src/activity/policy.js +436 -0
  75. package/dist/src/activity/policy.js.map +1 -0
  76. package/dist/src/activity/record.d.ts +7 -0
  77. package/dist/src/activity/record.d.ts.map +1 -0
  78. package/dist/src/activity/record.js +61 -0
  79. package/dist/src/activity/record.js.map +1 -0
  80. package/dist/src/activity/source-reader.d.ts +46 -0
  81. package/dist/src/activity/source-reader.d.ts.map +1 -0
  82. package/dist/src/activity/source-reader.js +594 -0
  83. package/dist/src/activity/source-reader.js.map +1 -0
  84. package/dist/src/activity/types.d.ts +21 -0
  85. package/dist/src/activity/types.d.ts.map +1 -0
  86. package/dist/src/activity/types.js +2 -0
  87. package/dist/src/activity/types.js.map +1 -0
  88. package/dist/src/agent-action-executor.d.ts +21 -0
  89. package/dist/src/agent-action-executor.d.ts.map +1 -0
  90. package/dist/src/agent-action-executor.js +248 -0
  91. package/dist/src/agent-action-executor.js.map +1 -0
  92. package/dist/src/agentic-guardian.d.ts +50 -0
  93. package/dist/src/agentic-guardian.d.ts.map +1 -0
  94. package/dist/src/agentic-guardian.js +220 -0
  95. package/dist/src/agentic-guardian.js.map +1 -0
  96. package/dist/src/atomic-replace.d.ts +4 -0
  97. package/dist/src/atomic-replace.d.ts.map +1 -0
  98. package/dist/src/atomic-replace.js +18 -0
  99. package/dist/src/atomic-replace.js.map +1 -0
  100. package/dist/src/cli.d.ts +11 -0
  101. package/dist/src/cli.d.ts.map +1 -0
  102. package/dist/src/cli.js +1244 -0
  103. package/dist/src/cli.js.map +1 -0
  104. package/dist/src/config.d.ts +68 -0
  105. package/dist/src/config.d.ts.map +1 -0
  106. package/dist/src/config.js +260 -0
  107. package/dist/src/config.js.map +1 -0
  108. package/dist/src/credential-store.d.ts +22 -0
  109. package/dist/src/credential-store.d.ts.map +1 -0
  110. package/dist/src/credential-store.js +91 -0
  111. package/dist/src/credential-store.js.map +1 -0
  112. package/dist/src/custom-agent-registry.d.ts +26 -0
  113. package/dist/src/custom-agent-registry.d.ts.map +1 -0
  114. package/dist/src/custom-agent-registry.js +288 -0
  115. package/dist/src/custom-agent-registry.js.map +1 -0
  116. package/dist/src/discovery/adapters/claude.d.ts +3 -0
  117. package/dist/src/discovery/adapters/claude.d.ts.map +1 -0
  118. package/dist/src/discovery/adapters/claude.js +26 -0
  119. package/dist/src/discovery/adapters/claude.js.map +1 -0
  120. package/dist/src/discovery/adapters/codex.d.ts +3 -0
  121. package/dist/src/discovery/adapters/codex.d.ts.map +1 -0
  122. package/dist/src/discovery/adapters/codex.js +23 -0
  123. package/dist/src/discovery/adapters/codex.js.map +1 -0
  124. package/dist/src/discovery/adapters/custom.d.ts +4 -0
  125. package/dist/src/discovery/adapters/custom.d.ts.map +1 -0
  126. package/dist/src/discovery/adapters/custom.js +24 -0
  127. package/dist/src/discovery/adapters/custom.js.map +1 -0
  128. package/dist/src/discovery/adapters/generic.d.ts +3 -0
  129. package/dist/src/discovery/adapters/generic.d.ts.map +1 -0
  130. package/dist/src/discovery/adapters/generic.js +126 -0
  131. package/dist/src/discovery/adapters/generic.js.map +1 -0
  132. package/dist/src/discovery/adapters/hermes.d.ts +3 -0
  133. package/dist/src/discovery/adapters/hermes.d.ts.map +1 -0
  134. package/dist/src/discovery/adapters/hermes.js +22 -0
  135. package/dist/src/discovery/adapters/hermes.js.map +1 -0
  136. package/dist/src/discovery/adapters/openclaw.d.ts +3 -0
  137. package/dist/src/discovery/adapters/openclaw.d.ts.map +1 -0
  138. package/dist/src/discovery/adapters/openclaw.js +22 -0
  139. package/dist/src/discovery/adapters/openclaw.js.map +1 -0
  140. package/dist/src/discovery/engine.d.ts +17 -0
  141. package/dist/src/discovery/engine.d.ts.map +1 -0
  142. package/dist/src/discovery/engine.js +164 -0
  143. package/dist/src/discovery/engine.js.map +1 -0
  144. package/dist/src/discovery/evidence.d.ts +29 -0
  145. package/dist/src/discovery/evidence.d.ts.map +1 -0
  146. package/dist/src/discovery/evidence.js +2 -0
  147. package/dist/src/discovery/evidence.js.map +1 -0
  148. package/dist/src/discovery/index.d.ts +11 -0
  149. package/dist/src/discovery/index.d.ts.map +1 -0
  150. package/dist/src/discovery/index.js +11 -0
  151. package/dist/src/discovery/index.js.map +1 -0
  152. package/dist/src/discovery/known-agent.d.ts +17 -0
  153. package/dist/src/discovery/known-agent.d.ts.map +1 -0
  154. package/dist/src/discovery/known-agent.js +193 -0
  155. package/dist/src/discovery/known-agent.js.map +1 -0
  156. package/dist/src/discovery/process-identity.d.ts +19 -0
  157. package/dist/src/discovery/process-identity.d.ts.map +1 -0
  158. package/dist/src/discovery/process-identity.js +73 -0
  159. package/dist/src/discovery/process-identity.js.map +1 -0
  160. package/dist/src/discovery/system-evidence.d.ts +9 -0
  161. package/dist/src/discovery/system-evidence.d.ts.map +1 -0
  162. package/dist/src/discovery/system-evidence.js +270 -0
  163. package/dist/src/discovery/system-evidence.js.map +1 -0
  164. package/dist/src/discovery/types.d.ts +21 -0
  165. package/dist/src/discovery/types.d.ts.map +1 -0
  166. package/dist/src/discovery/types.js +2 -0
  167. package/dist/src/discovery/types.js.map +1 -0
  168. package/dist/src/event-queue.d.ts +32 -0
  169. package/dist/src/event-queue.d.ts.map +1 -0
  170. package/dist/src/event-queue.js +125 -0
  171. package/dist/src/event-queue.js.map +1 -0
  172. package/dist/src/event-spool.d.ts +19 -0
  173. package/dist/src/event-spool.d.ts.map +1 -0
  174. package/dist/src/event-spool.js +350 -0
  175. package/dist/src/event-spool.js.map +1 -0
  176. package/dist/src/generated/phigon/v1/action_pb.d.ts +223 -0
  177. package/dist/src/generated/phigon/v1/action_pb.d.ts.map +1 -0
  178. package/dist/src/generated/phigon/v1/action_pb.js +96 -0
  179. package/dist/src/generated/phigon/v1/action_pb.js.map +1 -0
  180. package/dist/src/generated/phigon/v1/common_pb.d.ts +255 -0
  181. package/dist/src/generated/phigon/v1/common_pb.d.ts.map +1 -0
  182. package/dist/src/generated/phigon/v1/common_pb.js +242 -0
  183. package/dist/src/generated/phigon/v1/common_pb.js.map +1 -0
  184. package/dist/src/generated/phigon/v1/history_sync_pb.d.ts +119 -0
  185. package/dist/src/generated/phigon/v1/history_sync_pb.d.ts.map +1 -0
  186. package/dist/src/generated/phigon/v1/history_sync_pb.js +46 -0
  187. package/dist/src/generated/phigon/v1/history_sync_pb.js.map +1 -0
  188. package/dist/src/generated/phigon/v1/identity_pb.d.ts +235 -0
  189. package/dist/src/generated/phigon/v1/identity_pb.d.ts.map +1 -0
  190. package/dist/src/generated/phigon/v1/identity_pb.js +31 -0
  191. package/dist/src/generated/phigon/v1/identity_pb.js.map +1 -0
  192. package/dist/src/generated/phigon/v1/observer_service_pb.d.ts +362 -0
  193. package/dist/src/generated/phigon/v1/observer_service_pb.d.ts.map +1 -0
  194. package/dist/src/generated/phigon/v1/observer_service_pb.js +120 -0
  195. package/dist/src/generated/phigon/v1/observer_service_pb.js.map +1 -0
  196. package/dist/src/generated/phigon/v1/policy_pb.d.ts +456 -0
  197. package/dist/src/generated/phigon/v1/policy_pb.d.ts.map +1 -0
  198. package/dist/src/generated/phigon/v1/policy_pb.js +161 -0
  199. package/dist/src/generated/phigon/v1/policy_pb.js.map +1 -0
  200. package/dist/src/generated/phigon/v1/supervision_pb.d.ts +849 -0
  201. package/dist/src/generated/phigon/v1/supervision_pb.d.ts.map +1 -0
  202. package/dist/src/generated/phigon/v1/supervision_pb.js +384 -0
  203. package/dist/src/generated/phigon/v1/supervision_pb.js.map +1 -0
  204. package/dist/src/generated/phigon/v1/telemetry_pb.d.ts +761 -0
  205. package/dist/src/generated/phigon/v1/telemetry_pb.d.ts.map +1 -0
  206. package/dist/src/generated/phigon/v1/telemetry_pb.js +309 -0
  207. package/dist/src/generated/phigon/v1/telemetry_pb.js.map +1 -0
  208. package/dist/src/guardian-containment.d.ts +30 -0
  209. package/dist/src/guardian-containment.d.ts.map +1 -0
  210. package/dist/src/guardian-containment.js +113 -0
  211. package/dist/src/guardian-containment.js.map +1 -0
  212. package/dist/src/guardian-decision-journal.d.ts +29 -0
  213. package/dist/src/guardian-decision-journal.d.ts.map +1 -0
  214. package/dist/src/guardian-decision-journal.js +55 -0
  215. package/dist/src/guardian-decision-journal.js.map +1 -0
  216. package/dist/src/guardian-execution-manifest.d.ts +29 -0
  217. package/dist/src/guardian-execution-manifest.d.ts.map +1 -0
  218. package/dist/src/guardian-execution-manifest.js +95 -0
  219. package/dist/src/guardian-execution-manifest.js.map +1 -0
  220. package/dist/src/guardian-hosted-client.d.ts +10 -0
  221. package/dist/src/guardian-hosted-client.d.ts.map +1 -0
  222. package/dist/src/guardian-hosted-client.js +45 -0
  223. package/dist/src/guardian-hosted-client.js.map +1 -0
  224. package/dist/src/guardian-managed-exec.d.ts +21 -0
  225. package/dist/src/guardian-managed-exec.d.ts.map +1 -0
  226. package/dist/src/guardian-managed-exec.js +249 -0
  227. package/dist/src/guardian-managed-exec.js.map +1 -0
  228. package/dist/src/guardian-model-bridge.d.ts +13 -0
  229. package/dist/src/guardian-model-bridge.d.ts.map +1 -0
  230. package/dist/src/guardian-model-bridge.js +87 -0
  231. package/dist/src/guardian-model-bridge.js.map +1 -0
  232. package/dist/src/guardian-native.d.ts +3 -0
  233. package/dist/src/guardian-native.d.ts.map +1 -0
  234. package/dist/src/guardian-native.js +13 -0
  235. package/dist/src/guardian-native.js.map +1 -0
  236. package/dist/src/guardian-process-monitor.d.ts +20 -0
  237. package/dist/src/guardian-process-monitor.d.ts.map +1 -0
  238. package/dist/src/guardian-process-monitor.js +98 -0
  239. package/dist/src/guardian-process-monitor.js.map +1 -0
  240. package/dist/src/health.d.ts +35 -0
  241. package/dist/src/health.d.ts.map +1 -0
  242. package/dist/src/health.js +41 -0
  243. package/dist/src/health.js.map +1 -0
  244. package/dist/src/historical-identity-store.d.ts +20 -0
  245. package/dist/src/historical-identity-store.d.ts.map +1 -0
  246. package/dist/src/historical-identity-store.js +391 -0
  247. package/dist/src/historical-identity-store.js.map +1 -0
  248. package/dist/src/index.d.ts +14 -0
  249. package/dist/src/index.d.ts.map +1 -0
  250. package/dist/src/index.js +14 -0
  251. package/dist/src/index.js.map +1 -0
  252. package/dist/src/installation.d.ts +2 -0
  253. package/dist/src/installation.d.ts.map +1 -0
  254. package/dist/src/installation.js +44 -0
  255. package/dist/src/installation.js.map +1 -0
  256. package/dist/src/machine-command-executor.d.ts +28 -0
  257. package/dist/src/machine-command-executor.d.ts.map +1 -0
  258. package/dist/src/machine-command-executor.js +166 -0
  259. package/dist/src/machine-command-executor.js.map +1 -0
  260. package/dist/src/pair.d.ts +21 -0
  261. package/dist/src/pair.d.ts.map +1 -0
  262. package/dist/src/pair.js +125 -0
  263. package/dist/src/pair.js.map +1 -0
  264. package/dist/src/remove-machine.d.ts +15 -0
  265. package/dist/src/remove-machine.d.ts.map +1 -0
  266. package/dist/src/remove-machine.js +77 -0
  267. package/dist/src/remove-machine.js.map +1 -0
  268. package/dist/src/stream-client.d.ts +42 -0
  269. package/dist/src/stream-client.d.ts.map +1 -0
  270. package/dist/src/stream-client.js +867 -0
  271. package/dist/src/stream-client.js.map +1 -0
  272. package/dist/src/stream-state.d.ts +13 -0
  273. package/dist/src/stream-state.d.ts.map +1 -0
  274. package/dist/src/stream-state.js +128 -0
  275. package/dist/src/stream-state.js.map +1 -0
  276. package/dist/src/windows-process.d.ts +7 -0
  277. package/dist/src/windows-process.d.ts.map +1 -0
  278. package/dist/src/windows-process.js +17 -0
  279. package/dist/src/windows-process.js.map +1 -0
  280. package/package.json +78 -0
  281. package/scripts/install.mjs +308 -0
  282. package/scripts/managed-service.mjs +132 -0
  283. package/scripts/verify-guardian-release.mjs +42 -0
  284. 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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
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
+ }