sandbox.yml 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Sandbox CI: the keyless real-kernel confinement proofs. This master-only
  2. # reference stays outside the pull-request verdict; rationale lives in
  3. # .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md.
  4. # A separate workflow from ci.yml because the axis is different — these jobs
  5. # fan out over OS×runner (kernel capabilities), not node versions. The Landlock
  6. # launcher arrives from the registry with `pnpm install` (the npm package family
  7. # `node-addon-landlock-run`, built and released from its own repository), so
  8. # these legs exercise the true consumer path — nothing is compiled here.
  9. name: Sandbox
  10. on:
  11. push:
  12. branches: [master]
  13. concurrency:
  14. group: ${{ github.workflow }}-${{ github.ref }}
  15. cancel-in-progress: true
  16. permissions:
  17. contents: read
  18. env:
  19. # CI runs must never report to the production telemetry endpoint baked
  20. # into apps/cli/cordis.yml (AppCLIEntry disables the row when set).
  21. DSH_TELEMETRY_DISABLED: '1'
  22. jobs:
  23. # Keyless real-kernel sandbox proofs (sandbox Agent Note § Testing): each ladder
  24. # rung is only provable on a host where it enforces, so this job fans out
  25. # an OS×runner matrix — bwrap and Landlock on Linux (separate legs: the
  26. # Landlock files force the bwrap rung off, so each leg proves exactly one
  27. # rung; Landlock twice, once per architecture, each confining through the
  28. # registry-installed launcher), Seatbelt on macOS (sandbox-exec ships with
  29. # the OS). One node
  30. # version only: kernel confinement does not vary by node, and ci.yml's
  31. # node matrix already covers the node axis.
  32. #
  33. # The e2e files self-skip where their runner is absent, so a leg that lost
  34. # its runner (no bwrap, kernel without Landlock, macOS without
  35. # sandbox-exec) would otherwise pass as a false green — the same trap
  36. # e2e.yml's key preflight guards against. Each leg therefore asserts BOTH
  37. # its platform files actually ran: `Test Files 2 passed (2)`, no skips.
  38. sandbox-e2e:
  39. strategy:
  40. fail-fast: false
  41. matrix:
  42. include:
  43. - os: ubuntu-latest
  44. runner: bwrap
  45. - os: ubuntu-24.04
  46. runner: landlock
  47. - os: ubuntu-24.04-arm
  48. runner: landlock
  49. - os: macos-latest
  50. runner: seatbelt
  51. name: sandbox e2e (${{ matrix.runner }}, ${{ matrix.os }})
  52. runs-on: ${{ matrix.os }}
  53. timeout-minutes: 20
  54. steps:
  55. - uses: actions/checkout@v6
  56. - uses: pnpm/action-setup@v4
  57. - uses: actions/setup-node@v6
  58. with:
  59. node-version: 24
  60. - name: Install (immutable)
  61. run: pnpm install --frozen-lockfile
  62. # The bwrap rung needs bubblewrap on PATH and unprivileged user
  63. # namespaces. Ubuntu 24.04 gates the latter behind an AppArmor knob;
  64. # lift it best-effort — on images where the knob is absent the
  65. # functional probe (and the run-guard below) is the arbiter anyway.
  66. - name: Install bubblewrap (unrestrict userns)
  67. if: matrix.runner == 'bwrap'
  68. run: |
  69. sudo apt-get update -q
  70. sudo apt-get install -yq bubblewrap
  71. sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 \
  72. || echo "apparmor userns knob absent — the functional probe decides"
  73. # The unit suite runs on ubuntu in `checks`; this is the one darwin leg
  74. # in the workflow, so run it here too — the platform-dependent unit
  75. # expectations (Seatbelt path canonicalization: /tmp IS /private/tmp)
  76. # take their darwin branch only on this runner.
  77. - name: Unit tests (darwin parity)
  78. if: matrix.runner == 'seatbelt'
  79. run: pnpm run test
  80. - name: Sandbox e2e (real kernel confinement, world-verified)
  81. # NO_COLOR: vitest force-enables ANSI color under GITHUB_ACTIONS even
  82. # without a TTY, which would thread escape codes through the summary
  83. # line the run-guard greps.
  84. env:
  85. NO_COLOR: 1
  86. run: |
  87. set -u +e -o pipefail
  88. out=$(pnpm exec vitest run --config vitest.e2e.config.ts \
  89. packages/sandbox/sandbox-local/tests/${{ matrix.runner }}.e2e.ts \
  90. packages/bash/bash-sandbox/tests/${{ matrix.runner }}.e2e.ts 2>&1); status=$?
  91. echo "$out"
  92. [ "$status" -eq 0 ]
  93. # Both platform files must have RUN — a self-skip (runner missing on
  94. # the very platform that exists to prove it) is a failure, not a pass.
  95. echo "$out" | grep -qE 'Test Files[[:space:]]+2 passed \(2\)'
  96. # Publish-path rehearsal, Landlock legs only (the pack gates need built
  97. # lib/). The e2e packs the workspace closure, installs the tarballs
  98. # into a throwaway consumer — npm pulling `node-addon-landlock-run`
  99. # and its platform package from the registry, the true consumer path —
  100. # and confines through the INSTALLED launcher, asserting it executable
  101. # apart (a mode-stripped binary must not masquerade as a non-enforcing
  102. # kernel). Same no-silent-skip guard as above.
  103. - name: Build packages (lib/ for the pack rehearsal)
  104. if: matrix.runner == 'landlock'
  105. run: pnpm run build
  106. - name: Packed-distribution e2e (pack → install → confine)
  107. if: matrix.runner == 'landlock'
  108. env:
  109. NO_COLOR: 1
  110. run: |
  111. set -u +e -o pipefail
  112. out=$(pnpm exec vitest run --config vitest.e2e.config.ts \
  113. packages/sandbox/sandbox-local/tests/packed-install.e2e.ts 2>&1); status=$?
  114. echo "$out"
  115. [ "$status" -eq 0 ]
  116. echo "$out" | grep -qE 'Test Files[[:space:]]+1 passed \(1\)'