@holoscript/holosystem 0.2.4 → 0.2.5

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 CHANGED
@@ -521,6 +521,20 @@ kill-on-close Job Object with one-process, memory, and UI restrictions, and
521
521
  only then resumes it. A private low-integrity temporary directory is the only
522
522
  intended writable launch location.
523
523
 
524
+ `vm-launch-whpx-appcontainer` is a fourth, separately named vocabulary. It
525
+ retains those restricted-token and Job controls, gives an ephemeral Windows
526
+ AppContainer identity zero capabilities, grants that identity only read/execute
527
+ access to the measured launch snapshot and modify access to its private temp
528
+ directory, and requires file-read plus loopback-connect attack canaries to fail
529
+ before either WHPX launch begins. The plan cannot name a capability, path,
530
+ command, network mode, or fallback.
531
+
532
+ `vm-launch-appcontainer` uses the same zero-capability boundary with QEMU TCG.
533
+ It fixes the translation buffer at 64 MiB so the one-process 512 MiB Job limit
534
+ remains effective. This is the fully confined adapter proven on the validated
535
+ Windows host: it proves the machine VM and host file/network boundary, but sets
536
+ `hardwareBacked: false` and keeps hardware acceleration missing.
537
+
524
538
  First measure the complete caller-owned QEMU runtime closure and both guest
525
539
  artifacts:
526
540
 
@@ -596,6 +610,25 @@ process creation. The launcher source and local rebuild recipe are shipped in
596
610
  `native/windows-sandbox`; the measured binary is shipped in
597
611
  `native/windows-x64`.
598
612
 
613
+ For the AppContainer adapter, use schema
614
+ `holoscript.holosystem.whpx-appcontainer-vm-launch-plan.v1`, use the same
615
+ launcher digest with sandbox kind `windows-appcontainer-deny-v1`, and call
616
+ `vm-launch-whpx-appcontainer`. The launcher creates and deletes a fresh profile
617
+ for each launch. Its closed receipt protocol must prove the child AppContainer
618
+ SID, zero capabilities, low-integrity filtered token, snapshot/temp grants,
619
+ access-denied (`5`) for a protected caller-readable file, plus either explicit
620
+ WSA access-denied (`10013`) or a bounded WFP-style packet-drop timeout (`10060`)
621
+ with no accepted live loopback connection. Any missing or changed observation
622
+ blocks the receipt.
623
+
624
+ Use schema `holoscript.holosystem.appcontainer-vm-launch-plan.v1`, target
625
+ accelerator `tcg`, the same two sandbox digests, and command
626
+ `vm-launch-appcontainer` for the software-emulated confined lane. On the
627
+ validated Windows 11 host, the WHPX AppContainer form fails closed because
628
+ Windows returns `0x80070005` when zero-capability QEMU initializes WHPX. The
629
+ package does not merge that result with the separately proven hardware-backed
630
+ low-integrity WHPX receipt.
631
+
599
632
  The runner creates a private snapshot and remeasures the complete QEMU closure,
600
633
  kernel, and initramfs before and after each launch. It then generates QEMU
601
634
  arguments that disable user configuration, default devices, networking, USB,
@@ -629,15 +662,31 @@ boundary. The sandboxed receipt also does not prove the broader hardware and
629
662
  firmware properties named above. The
630
663
  exact claims are in the [VM launch threat model](./docs/vm-launch-threat-model.md).
631
664
 
665
+ A verified `vm-launch-whpx-appcontainer` receipt additionally includes
666
+ `host-filesystem-confidentiality` and `host-network-isolation` for the tested
667
+ zero-capability AppContainer boundary. Those are scoped claims: they do not
668
+ establish QEMU provenance, host-kernel correctness, IOMMU separation, measured
669
+ boot, firmware authenticity, crash-dump custody, confidential memory, or
670
+ side-channel resistance.
671
+
672
+ A verified `vm-launch-appcontainer` receipt includes the same process,
673
+ filesystem, and network layers while leaving
674
+ `hardware-hypervisor-acceleration` missing. Receipts describe one executed
675
+ boundary, not the union of properties observed in different launches.
676
+
632
677
  ```js
633
678
  import {
634
679
  inspectVmExecutor,
680
+ inspectAppContainerVmLaunchPlan,
635
681
  inspectVmLaunchAsset,
636
682
  inspectVmLaunchPlan,
637
683
  inspectWindowsVmSandboxLauncher,
684
+ inspectWhpxAppContainerVmLaunchPlan,
638
685
  inspectWhpxSandboxedVmLaunchPlan,
639
686
  inspectWhpxVmLaunchPlan,
640
687
  runVmLaunch,
688
+ runAppContainerVmLaunch,
689
+ runWhpxAppContainerVmLaunch,
641
690
  runWhpxSandboxedVmLaunch,
642
691
  runWhpxVmLaunch,
643
692
  } from '@holoscript/holosystem';
@@ -14,7 +14,9 @@ import {
14
14
  inspectVmExecutor,
15
15
  inspectVmLaunchAsset,
16
16
  runNativeBuild,
17
+ runAppContainerVmLaunch,
17
18
  runVmLaunch,
19
+ runWhpxAppContainerVmLaunch,
18
20
  runWhpxSandboxedVmLaunch,
19
21
  runWhpxVmLaunch,
20
22
  } from '../src/index.mjs';
@@ -35,8 +37,10 @@ Usage:
35
37
  holosystem vm-executor --runtime <directory> [--json]
36
38
  holosystem vm-asset --kind <kernel|initrd> --file <file> [--json]
37
39
  holosystem vm-launch --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
40
+ holosystem vm-launch-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
38
41
  holosystem vm-launch-whpx --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
39
42
  holosystem vm-launch-whpx-sandboxed --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
43
+ holosystem vm-launch-whpx-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
40
44
  holosystem substrate --input <file> [--output <file>] [--force] [--json]
41
45
  holosystem --help
42
46
  holosystem --version
@@ -703,6 +707,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
703
707
  runVmAsset(argv.slice(1));
704
708
  } else if (command === 'vm-launch') {
705
709
  runVmLaunchCommand(argv.slice(1));
710
+ } else if (command === 'vm-launch-appcontainer') {
711
+ runVmLaunchCommand(argv.slice(1), {
712
+ launcher: runAppContainerVmLaunch,
713
+ commandName: 'vm-launch-appcontainer',
714
+ });
706
715
  } else if (command === 'vm-launch-whpx') {
707
716
  runVmLaunchCommand(argv.slice(1), {
708
717
  launcher: runWhpxVmLaunch,
@@ -713,6 +722,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
713
722
  launcher: runWhpxSandboxedVmLaunch,
714
723
  commandName: 'vm-launch-whpx-sandboxed',
715
724
  });
725
+ } else if (command === 'vm-launch-whpx-appcontainer') {
726
+ runVmLaunchCommand(argv.slice(1), {
727
+ launcher: runWhpxAppContainerVmLaunch,
728
+ commandName: 'vm-launch-whpx-appcontainer',
729
+ });
716
730
  } else if (command === 'substrate') {
717
731
  runSubstrate(argv.slice(1));
718
732
  } else {
@@ -27,6 +27,25 @@ full Job UI restrictions. Its `host-process-isolation` claim is limited to
27
27
  those controls. It is not an AppContainer and does not claim host filesystem
28
28
  confidentiality or host network isolation.
29
29
 
30
+ `vm-launch-whpx-appcontainer` is another separate schema and receipt. It adds
31
+ an ephemeral zero-capability AppContainer identity to the same filtered,
32
+ low-integrity token and pre-resume Job boundary. The identity receives inherited
33
+ read/execute access to the private measured snapshot and modify access only to
34
+ the private temp directory. Before QEMU starts, the launcher runs an
35
+ AppContainer child under the same token and verifies that it cannot read a
36
+ caller-readable protected sentinel or connect to a live loopback listener. The
37
+ receipt claims filesystem confidentiality and network isolation only when the
38
+ token identity, zero capability count, exact denial errors, and both WHPX boots
39
+ all verify.
40
+
41
+ `vm-launch-appcontainer` applies the same boundary to TCG with a fixed 64 MiB
42
+ translation buffer. It can claim process, filesystem, and network isolation
43
+ after two deterministic boots, but it cannot claim hardware acceleration. On
44
+ the validated Windows 11 host, zero-capability QEMU receives access denied
45
+ (`0x80070005`) while initializing WHPX. That negative result is preserved as a
46
+ blocked WHPX AppContainer receipt; it is never combined with the verified TCG
47
+ AppContainer receipt or the separately verified low-integrity WHPX receipt.
48
+
30
49
  ## Protected assets and trust boundaries
31
50
 
32
51
  The protected inputs are the declarative plan, the complete caller-owned QEMU
@@ -65,6 +84,10 @@ than implied away by a successful boot.
65
84
  | Select ambient execution, weaken a sandbox control, change the launcher, or forge its protocol | Reject the distinct sandbox schema, launcher digest, or incomplete evidence | Closed sandbox vocabulary and protocol-forgery tests |
66
85
  | Pass inherited parent handles into sandboxed QEMU | Admit only NUL input and the two bounded output pipes | Native handle-list evidence and receipt assertion |
67
86
  | Spawn another process or escape launcher lifetime/resource bounds | Assign suspended QEMU before resume to a one-process, kill-on-close Job | Native Job evidence and adversarial receipt tests |
87
+ | Add an AppContainer capability, path grant, network mode, command, or fallback | Reject the distinct closed plan vocabulary before launch | AppContainer closed-vocabulary test |
88
+ | Forge the AppContainer SID, capability count, profile cleanup, or grant evidence | Reject the closed native protocol | AppContainer protocol-forgery test |
89
+ | Read a caller-readable file outside the measured snapshot | Require exact access-denied evidence from a zero-capability AppContainer | Protected-sentinel canary and receipt assertion |
90
+ | Connect to the host loopback listener from compromised QEMU | Require bounded WSA deny/drop evidence and no accepted connection | Loopback canary and receipt assertion |
68
91
  | Put an operational path or guest output in the receipt | Withhold it; report only digest and byte count | Receipt disclosure assertions |
69
92
 
70
93
  ## Fixed launch policy
@@ -93,6 +116,20 @@ rejects unknown fields, non-canonical base64, an unbounded enabled-privilege
93
116
  count, a missing control, or launcher stderr. Launcher and QEMU snapshots are
94
117
  remeasured before and after both launches.
95
118
 
119
+ The AppContainer adapter uses the same measured launcher but a distinct plan,
120
+ protocol, and control vocabulary. It never accepts caller-specified capabilities
121
+ or ACL paths. Each native invocation creates a fresh AppContainer profile,
122
+ applies the fixed grants, runs both canaries, creates QEMU with the same zero
123
+ capability `SECURITY_CAPABILITIES`, checks the suspended child token, and deletes
124
+ the profile during cleanup. HoloSystem rejects a nonzero capability count,
125
+ unexpected bounded denial code, accepted loopback connection, incomplete cleanup, or
126
+ any unknown protocol field.
127
+
128
+ The TCG AppContainer plan also fixes `tcg,tb-size=64`; a caller cannot enlarge
129
+ the JIT translation buffer or relax the 512 MiB process limit. Its receipt keeps
130
+ `hardware-hypervisor-acceleration` missing even when every confinement canary
131
+ passes.
132
+
96
133
  ## Residual risk and next layer
97
134
 
98
135
  The QEMU runtime closure includes firmware data files and DLLs, so substitution
@@ -114,8 +151,9 @@ isolation controls verify. The launcher binary is measured, but source-to-binary
114
151
  reproducibility and code signing remain part of `qemu-runtime-supply-chain` and
115
152
  host correctness trust.
116
153
 
117
- The next stronger layer is an AppContainer or separate-host-VM adapter that can
118
- retain WHPX access while proving capability-scoped filesystem and network
119
- denial, followed by firmware/measured-boot evidence. It must continue to report
120
- IOMMU, device-assignment, crash-dump custody, and side-channel boundaries
121
- separately.
154
+ The TCG AppContainer tracer implements capability-scoped filesystem and network
155
+ denial without hardware acceleration; the WHPX form remains blocked on the
156
+ validated host. It does not turn the Windows kernel into a separate trust
157
+ domain. The next layers are a WHPX broker or independent host boundary and
158
+ firmware/measured-boot evidence. IOMMU, device-assignment, crash-dump custody,
159
+ host correctness, and side-channel boundaries remain separate.
@@ -0,0 +1,107 @@
1
+ #define WIN32_LEAN_AND_MEAN
2
+ #include <winsock2.h>
3
+ #include <windows.h>
4
+ #include <stdio.h>
5
+ #include <wchar.h>
6
+
7
+ static int read_token_integer(HANDLE token, TOKEN_INFORMATION_CLASS information_class, int fallback)
8
+ {
9
+ DWORD required = 0;
10
+ BYTE *buffer;
11
+ int value;
12
+ GetTokenInformation(token, information_class, NULL, 0, &required);
13
+ if (required == 0) return fallback;
14
+ buffer = (BYTE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, required);
15
+ if (buffer == NULL) return fallback;
16
+ if (!GetTokenInformation(token, information_class, buffer, required, &required)) {
17
+ HeapFree(GetProcessHeap(), 0, buffer);
18
+ return fallback;
19
+ }
20
+ value = *(int *)buffer;
21
+ HeapFree(GetProcessHeap(), 0, buffer);
22
+ return value;
23
+ }
24
+
25
+ int wmain(int argc, wchar_t **argv)
26
+ {
27
+ HANDLE file = INVALID_HANDLE_VALUE;
28
+ HANDLE token = NULL;
29
+ WSADATA socket_data;
30
+ SOCKET socket_handle = INVALID_SOCKET;
31
+ struct sockaddr_in address;
32
+ fd_set writable;
33
+ fd_set exceptional;
34
+ struct timeval wait_time;
35
+ u_long nonblocking = 1;
36
+ int port;
37
+ int filesystem_error = 0;
38
+ int network_error = 0;
39
+ int app_container = 0;
40
+ int capability_count = -1;
41
+
42
+ if (argc != 5 || wcscmp(argv[1], L"--protected-sentinel") != 0 ||
43
+ wcscmp(argv[3], L"--loopback-port") != 0) return 64;
44
+ port = _wtoi(argv[4]);
45
+ if (port < 1 || port > 65535) return 64;
46
+
47
+ file = CreateFileW(
48
+ argv[2], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
49
+ FILE_ATTRIBUTE_NORMAL, NULL
50
+ );
51
+ if (file == INVALID_HANDLE_VALUE) filesystem_error = (int)GetLastError();
52
+ else CloseHandle(file);
53
+
54
+ if (WSAStartup(MAKEWORD(2, 2), &socket_data) != 0) {
55
+ network_error = WSAGetLastError();
56
+ } else {
57
+ socket_handle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
58
+ if (socket_handle == INVALID_SOCKET) {
59
+ network_error = WSAGetLastError();
60
+ } else {
61
+ ioctlsocket(socket_handle, FIONBIO, &nonblocking);
62
+ ZeroMemory(&address, sizeof(address));
63
+ address.sin_family = AF_INET;
64
+ address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
65
+ address.sin_port = htons((u_short)port);
66
+ if (connect(socket_handle, (struct sockaddr *)&address, sizeof(address)) == SOCKET_ERROR) {
67
+ network_error = WSAGetLastError();
68
+ if (network_error == WSAEWOULDBLOCK) {
69
+ FD_ZERO(&writable);
70
+ FD_ZERO(&exceptional);
71
+ FD_SET(socket_handle, &writable);
72
+ FD_SET(socket_handle, &exceptional);
73
+ wait_time.tv_sec = 2;
74
+ wait_time.tv_usec = 0;
75
+ if (select(0, NULL, &writable, &exceptional, &wait_time) > 0) {
76
+ int socket_error = 0;
77
+ int error_length = sizeof(socket_error);
78
+ if (getsockopt(
79
+ socket_handle, SOL_SOCKET, SO_ERROR,
80
+ (char *)&socket_error, &error_length
81
+ ) == 0) network_error = socket_error;
82
+ else network_error = WSAGetLastError();
83
+ } else {
84
+ network_error = WSAETIMEDOUT;
85
+ }
86
+ }
87
+ }
88
+ closesocket(socket_handle);
89
+ }
90
+ WSACleanup();
91
+ }
92
+
93
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
94
+ app_container = read_token_integer(token, TokenIsAppContainer, 0);
95
+ capability_count = read_token_integer(token, TokenCapabilities, -1);
96
+ CloseHandle(token);
97
+ }
98
+
99
+ printf(
100
+ "appContainer=%d;capabilityCount=%d;filesystemError=%d;networkError=%d\n",
101
+ app_container != 0 ? 1 : 0,
102
+ capability_count,
103
+ filesystem_error,
104
+ network_error
105
+ );
106
+ return 0;
107
+ }