snapshot-workspace-parent.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. /** Workspace placement for snapshots that must not inherit temporary-directory write grants. */
  2. import { accessSync, constants } from 'node:fs'
  3. import { homedir, tmpdir } from 'node:os'
  4. import { dirname, isAbsolute, parse, relative, sep } from 'node:path'
  5. import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox'
  6. function contains(root: string, path: string): boolean {
  7. const suffix = relative(root, path)
  8. return suffix === '' || suffix !== '..' && !suffix.startsWith('..' + sep) && !isAbsolute(suffix)
  9. }
  10. /**
  11. * Select a writable temp sibling parent, or home when that parent is unavailable for writes.
  12. * The caller atomically allocates and owns cleanup of the generated workspace.
  13. * @param tempRoot - platform temporary directory.
  14. * @param home - fallback when temp siblings require a system directory or a non-writable parent.
  15. * @returns existing parent outside the automatic temporary write grants.
  16. */
  17. export function outsideTempWorkspaceParent(tempRoot = tmpdir(), home = homedir()): string {
  18. const temporary = canonicalPath(tempRoot)
  19. const systemTemporary = canonicalPath('/tmp')
  20. const parent = dirname(temporary)
  21. if (temporary === systemTemporary || parent === parse(parent).root || contains(systemTemporary, parent)) return home
  22. try {
  23. accessSync(parent, constants.W_OK)
  24. } catch (error) {
  25. // A non-writable parent cannot host siblings; allocation failures still propagate from mkdtemp.
  26. const code = (error as NodeJS.ErrnoException).code
  27. if (code === 'EACCES' || code === 'EPERM' || code === 'EROFS') return home
  28. throw error
  29. }
  30. return parent
  31. }
  32. /**
  33. * Reject a workspace whose write could succeed without the session's workspace grant.
  34. * @param cwd - allocated workspace to check, with symlinks resolved before comparison.
  35. * @returns nothing; throws when an automatic temporary write grant contains the workspace.
  36. */
  37. export function assertWorkspaceOutsideTemp(cwd: string): void {
  38. const path = canonicalPath(cwd)
  39. for (const root of writableRoots({ mode: 'workspace-write', workspaceRoot: '/tmp' })) {
  40. if (contains(root, path)) throw new Error('snapshot workspace ' + cwd + ' must be outside temporary writable root ' + root)
  41. }
  42. }