permission-denials.integration.test.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. // Integration test for CLAUDE.md §7.3 — customer code that tries to step
  2. // outside the per-isolate sandbox (read /etc, run subprocesses, peek at
  3. // host env) must be denied at the Deno permission layer. The runtime
  4. // surfaces the denial back as a normal value here (the customer code
  5. // catches the error) so we're testing that the denial mechanism fires —
  6. // not how the runtime maps it to a RuntimeErrorCode.
  7. import { describe, expect, test } from 'bun:test';
  8. import { runIntegrationFixture } from './test-helpers.js';
  9. describe('permission denials (integration)', () => {
  10. test('reading outside /tmp/<id> denied', async () => {
  11. const { result, cleanup } = await runIntegrationFixture({
  12. fnName: 'test',
  13. deploymentId: 'd1',
  14. fnSource: `
  15. import { query } from '@briven/cli/server';
  16. export const test = query(async () => {
  17. try {
  18. await Deno.readTextFile('/etc/passwd');
  19. return 'ok';
  20. } catch (e) {
  21. return (e as Error).name;
  22. }
  23. });
  24. `,
  25. });
  26. try {
  27. expect(result.ok).toBe(true);
  28. if (result.ok) {
  29. // Deno 2.x may use NotCapable, PermissionDenied, or similar; accept the family.
  30. expect(['PermissionDenied', 'NotCapable']).toContain(result.value);
  31. }
  32. } finally {
  33. await cleanup();
  34. }
  35. }, 30_000);
  36. test('Deno.Command (subprocess) denied', async () => {
  37. const { result, cleanup } = await runIntegrationFixture({
  38. fnName: 'test',
  39. deploymentId: 'd1',
  40. fnSource: `
  41. import { query } from '@briven/cli/server';
  42. export const test = query(async () => {
  43. try {
  44. const cmd = new Deno.Command('ls');
  45. await cmd.output();
  46. return 'ok';
  47. } catch (e) {
  48. return (e as Error).name;
  49. }
  50. });
  51. `,
  52. });
  53. try {
  54. expect(result.ok).toBe(true);
  55. if (result.ok) {
  56. expect(['PermissionDenied', 'NotCapable']).toContain(result.value);
  57. }
  58. } finally {
  59. await cleanup();
  60. }
  61. }, 30_000);
  62. test('env access for unallowed key returns undefined or denies', async () => {
  63. const { result, cleanup } = await runIntegrationFixture({
  64. fnName: 'test',
  65. deploymentId: 'd1',
  66. fnSource: `
  67. import { query } from '@briven/cli/server';
  68. export const test = query(async () => {
  69. try {
  70. return Deno.env.get('NOT_ALLOWED_KEY') ?? 'undefined';
  71. } catch (e) {
  72. return (e as Error).name;
  73. }
  74. });
  75. `,
  76. });
  77. try {
  78. expect(result.ok).toBe(true);
  79. if (result.ok) {
  80. // Either Deno returns undefined when --allow-env list is empty (no allowlist
  81. // → no access), or it denies. Both prove the customer can't reach env.
  82. expect(['undefined', 'PermissionDenied', 'NotCapable']).toContain(result.value);
  83. }
  84. } finally {
  85. await cleanup();
  86. }
  87. }, 30_000);
  88. });