sandbox.yml 5.2 KB

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