@holoscript/holosystem 0.2.4 → 0.2.6

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
@@ -55,6 +55,48 @@ returns one highest-priority candidate, and carries authority, validation, lease
55
55
  and spend stop conditions. It selects work; it does not claim a board task,
56
56
  publish a package, spend funds, or bypass caller authority.
57
57
 
58
+ ## HoloScript Source Canon
59
+
60
+ `source-canon` is the consumer-side language-sovereignty gate. Run it from the
61
+ root of a Git repository:
62
+
63
+ ```bash
64
+ npx holosystem source-canon \
65
+ --output runtime/visual-state/source-canon.hsplus \
66
+ --json
67
+ ```
68
+
69
+ The gate reads the fixed Git-tracked file inventory and accepts only `.holo`,
70
+ `.hs`, and `.hsplus` as canonical authored source. It then parses the actual
71
+ tracked bytes with the exact optional peer `@holoscript/core@8.0.14`; when that
72
+ parser is unavailable, the gate fails closed. This prevents JavaScript or another
73
+ language from becoming canonical merely by being renamed. Other HoloSystem
74
+ commands remain usable without the optional peer.
75
+
76
+ A caller cannot widen the registry with an extension allowlist. A future format
77
+ becomes canonical only when a HoloScript release owns its parser and adds it to
78
+ the package registry.
79
+
80
+ The command exits `0` only when at least one HoloScript source file exists and
81
+ every tracked file uses a canonical format. It exits `2` with every foreign
82
+ tracked path named otherwise. When `--output` is supplied, the durable
83
+ founder-visible projection must itself be a portable repository-relative
84
+ `.hsplus` path; JSON remains available only on standard output for transient
85
+ agent pipelines.
86
+
87
+ This gate audits Git-tracked canon. Dependency caches, runtime receipts, and
88
+ untracked generated artifacts are not silently promoted to source truth. They
89
+ remain non-canonical operational state and must stay untracked or move to an
90
+ external runtime store as the consumer migration proceeds.
91
+
92
+ ```js
93
+ import {
94
+ inspectGitTrackedSourceCanon,
95
+ inspectSourceCanon,
96
+ renderSourceCanonProjection,
97
+ } from '@holoscript/holosystem';
98
+ ```
99
+
58
100
  ## Substrate Closure
59
101
 
60
102
  The substrate closure replaces an implicit operating-system dependency tower
@@ -521,6 +563,20 @@ kill-on-close Job Object with one-process, memory, and UI restrictions, and
521
563
  only then resumes it. A private low-integrity temporary directory is the only
522
564
  intended writable launch location.
523
565
 
566
+ `vm-launch-whpx-appcontainer` is a fourth, separately named vocabulary. It
567
+ retains those restricted-token and Job controls, gives an ephemeral Windows
568
+ AppContainer identity zero capabilities, grants that identity only read/execute
569
+ access to the measured launch snapshot and modify access to its private temp
570
+ directory, and requires file-read plus loopback-connect attack canaries to fail
571
+ before either WHPX launch begins. The plan cannot name a capability, path,
572
+ command, network mode, or fallback.
573
+
574
+ `vm-launch-appcontainer` uses the same zero-capability boundary with QEMU TCG.
575
+ It fixes the translation buffer at 64 MiB so the one-process 512 MiB Job limit
576
+ remains effective. This is the fully confined adapter proven on the validated
577
+ Windows host: it proves the machine VM and host file/network boundary, but sets
578
+ `hardwareBacked: false` and keeps hardware acceleration missing.
579
+
524
580
  First measure the complete caller-owned QEMU runtime closure and both guest
525
581
  artifacts:
526
582
 
@@ -596,6 +652,25 @@ process creation. The launcher source and local rebuild recipe are shipped in
596
652
  `native/windows-sandbox`; the measured binary is shipped in
597
653
  `native/windows-x64`.
598
654
 
655
+ For the AppContainer adapter, use schema
656
+ `holoscript.holosystem.whpx-appcontainer-vm-launch-plan.v1`, use the same
657
+ launcher digest with sandbox kind `windows-appcontainer-deny-v1`, and call
658
+ `vm-launch-whpx-appcontainer`. The launcher creates and deletes a fresh profile
659
+ for each launch. Its closed receipt protocol must prove the child AppContainer
660
+ SID, zero capabilities, low-integrity filtered token, snapshot/temp grants,
661
+ access-denied (`5`) for a protected caller-readable file, plus either explicit
662
+ WSA access-denied (`10013`) or a bounded WFP-style packet-drop timeout (`10060`)
663
+ with no accepted live loopback connection. Any missing or changed observation
664
+ blocks the receipt.
665
+
666
+ Use schema `holoscript.holosystem.appcontainer-vm-launch-plan.v1`, target
667
+ accelerator `tcg`, the same two sandbox digests, and command
668
+ `vm-launch-appcontainer` for the software-emulated confined lane. On the
669
+ validated Windows 11 host, the WHPX AppContainer form fails closed because
670
+ Windows returns `0x80070005` when zero-capability QEMU initializes WHPX. The
671
+ package does not merge that result with the separately proven hardware-backed
672
+ low-integrity WHPX receipt.
673
+
599
674
  The runner creates a private snapshot and remeasures the complete QEMU closure,
600
675
  kernel, and initramfs before and after each launch. It then generates QEMU
601
676
  arguments that disable user configuration, default devices, networking, USB,
@@ -629,15 +704,31 @@ boundary. The sandboxed receipt also does not prove the broader hardware and
629
704
  firmware properties named above. The
630
705
  exact claims are in the [VM launch threat model](./docs/vm-launch-threat-model.md).
631
706
 
707
+ A verified `vm-launch-whpx-appcontainer` receipt additionally includes
708
+ `host-filesystem-confidentiality` and `host-network-isolation` for the tested
709
+ zero-capability AppContainer boundary. Those are scoped claims: they do not
710
+ establish QEMU provenance, host-kernel correctness, IOMMU separation, measured
711
+ boot, firmware authenticity, crash-dump custody, confidential memory, or
712
+ side-channel resistance.
713
+
714
+ A verified `vm-launch-appcontainer` receipt includes the same process,
715
+ filesystem, and network layers while leaving
716
+ `hardware-hypervisor-acceleration` missing. Receipts describe one executed
717
+ boundary, not the union of properties observed in different launches.
718
+
632
719
  ```js
633
720
  import {
634
721
  inspectVmExecutor,
722
+ inspectAppContainerVmLaunchPlan,
635
723
  inspectVmLaunchAsset,
636
724
  inspectVmLaunchPlan,
637
725
  inspectWindowsVmSandboxLauncher,
726
+ inspectWhpxAppContainerVmLaunchPlan,
638
727
  inspectWhpxSandboxedVmLaunchPlan,
639
728
  inspectWhpxVmLaunchPlan,
640
729
  runVmLaunch,
730
+ runAppContainerVmLaunch,
731
+ runWhpxAppContainerVmLaunch,
641
732
  runWhpxSandboxedVmLaunch,
642
733
  runWhpxVmLaunch,
643
734
  } from '@holoscript/holosystem';
@@ -700,6 +791,9 @@ npx holosystem inspect holosystem.config.json --json
700
791
 
701
792
  # Pipeline input is supported.
702
793
  npx holosystem create --stdout | npx holosystem inspect - --json
794
+
795
+ # Prove that Git-tracked authoring is HoloScript-only.
796
+ npx holosystem source-canon --output source-canon.hsplus --json
703
797
  ```
704
798
 
705
799
  Creation never installs packages, connects to storage, acquires credentials, or
@@ -10,13 +10,17 @@ import {
10
10
  importDebianPackageSnapshot,
11
11
  importNpmPackageLock,
12
12
  inspectNativeBuildSource,
13
+ inspectGitTrackedSourceCanon,
13
14
  inspectHoloSystemConfig,
14
15
  inspectVmExecutor,
15
16
  inspectVmLaunchAsset,
16
17
  runNativeBuild,
18
+ runAppContainerVmLaunch,
17
19
  runVmLaunch,
20
+ runWhpxAppContainerVmLaunch,
18
21
  runWhpxSandboxedVmLaunch,
19
22
  runWhpxVmLaunch,
23
+ renderSourceCanonProjection,
20
24
  } from '../src/index.mjs';
21
25
 
22
26
  const CLI_RECEIPT_SCHEMA = 'holoscript.holosystem.cli-receipt.v1';
@@ -32,11 +36,14 @@ Usage:
32
36
  holosystem substrate-import-debian --status <status> (--packages <Packages> | --sources <json>) --maintainer-scripts <json> --config <file> [--output <file>] [--force] [--json]
33
37
  holosystem native-build-source --source <directory> [--json]
34
38
  holosystem native-build --plan <file> --source <directory> --executor <file> --artifact-dir <directory> [--output <receipt>] [--force] [--json]
39
+ holosystem source-canon [--output <projection.hsplus>] [--force] [--json]
35
40
  holosystem vm-executor --runtime <directory> [--json]
36
41
  holosystem vm-asset --kind <kernel|initrd> --file <file> [--json]
37
42
  holosystem vm-launch --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
43
+ holosystem vm-launch-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
38
44
  holosystem vm-launch-whpx --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
39
45
  holosystem vm-launch-whpx-sandboxed --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
46
+ holosystem vm-launch-whpx-appcontainer --plan <file> --runtime <directory> --kernel <file> --initrd <file> [--output <receipt>] [--force] [--json]
40
47
  holosystem substrate --input <file> [--output <file>] [--force] [--json]
41
48
  holosystem --help
42
49
  holosystem --version
@@ -202,6 +209,63 @@ function writeJsonOutput(path, value, { force = false } = {}) {
202
209
  writeFileSync(absolute, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
203
210
  }
204
211
 
212
+ function writeSourceCanonProjection(path, value, { force = false } = {}) {
213
+ if (
214
+ typeof path !== 'string' ||
215
+ !path.endsWith('.hsplus') ||
216
+ /^[A-Za-z]:[\\/]/u.test(path) ||
217
+ /^(?:[\\/]{1,2}|~[\\/])/u.test(path) ||
218
+ path.replaceAll('\\', '/').split('/').some((segment) => segment === '..' || segment === '.')
219
+ ) {
220
+ throw new Error('--output must be a portable repository-relative .hsplus path.');
221
+ }
222
+ const absolute = resolve(process.cwd(), path);
223
+ if (existsSync(absolute) && !force)
224
+ throw new Error(`${path} already exists; use --force to replace it.`);
225
+ writeFileSync(absolute, value, 'utf8');
226
+ }
227
+
228
+ async function runSourceCanon(args) {
229
+ let parsed;
230
+ try {
231
+ parsed = parseArguments(args, {
232
+ output: 'value',
233
+ force: 'boolean',
234
+ json: 'boolean',
235
+ });
236
+ } catch (error) {
237
+ die(error.message, { json: args.includes('--json') });
238
+ }
239
+ const { options, positionals } = parsed;
240
+ if (positionals.length > 0) {
241
+ die('source-canon does not accept positional arguments.', { json: options.json });
242
+ }
243
+
244
+ let report;
245
+ try {
246
+ report = await inspectGitTrackedSourceCanon({ rootDirectory: process.cwd() });
247
+ if (options.output) {
248
+ writeSourceCanonProjection(options.output, renderSourceCanonProjection(report), {
249
+ force: options.force,
250
+ });
251
+ }
252
+ } catch (error) {
253
+ die(`Cannot inspect source canon: ${error.message}`, { json: options.json, code: 2 });
254
+ }
255
+
256
+ if (options.json) outputJson(report);
257
+ else {
258
+ process.stdout.write(
259
+ `Source canon: ${report.status} HoloScript=${report.summary.holoScriptFiles} foreign=${report.summary.foreignFiles}\n`
260
+ );
261
+ for (const issue of report.issues) {
262
+ process.stdout.write(`BLOCK ${issue.code} ${issue.path}: ${issue.message}\n`);
263
+ }
264
+ if (options.output) process.stdout.write(`Wrote ${options.output}\n`);
265
+ }
266
+ if (!report.verified) process.exitCode = 2;
267
+ }
268
+
205
269
  function runInspect(args) {
206
270
  let parsed;
207
271
  try {
@@ -697,12 +761,19 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
697
761
  runNativeBuildSource(argv.slice(1));
698
762
  } else if (command === 'native-build') {
699
763
  runNativeBuildCommand(argv.slice(1));
764
+ } else if (command === 'source-canon') {
765
+ await runSourceCanon(argv.slice(1));
700
766
  } else if (command === 'vm-executor') {
701
767
  runVmExecutor(argv.slice(1));
702
768
  } else if (command === 'vm-asset') {
703
769
  runVmAsset(argv.slice(1));
704
770
  } else if (command === 'vm-launch') {
705
771
  runVmLaunchCommand(argv.slice(1));
772
+ } else if (command === 'vm-launch-appcontainer') {
773
+ runVmLaunchCommand(argv.slice(1), {
774
+ launcher: runAppContainerVmLaunch,
775
+ commandName: 'vm-launch-appcontainer',
776
+ });
706
777
  } else if (command === 'vm-launch-whpx') {
707
778
  runVmLaunchCommand(argv.slice(1), {
708
779
  launcher: runWhpxVmLaunch,
@@ -713,6 +784,11 @@ if (!command || command === '--help' || command === '-h' || command === 'help')
713
784
  launcher: runWhpxSandboxedVmLaunch,
714
785
  commandName: 'vm-launch-whpx-sandboxed',
715
786
  });
787
+ } else if (command === 'vm-launch-whpx-appcontainer') {
788
+ runVmLaunchCommand(argv.slice(1), {
789
+ launcher: runWhpxAppContainerVmLaunch,
790
+ commandName: 'vm-launch-whpx-appcontainer',
791
+ });
716
792
  } else if (command === 'substrate') {
717
793
  runSubstrate(argv.slice(1));
718
794
  } 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
+ }