isolation.ts 1.8 KB

1234567891011121314151617181920212223
  1. import {execFileSync} from 'node:child_process'
  2. import {z} from 'zod'
  3. import {readPrivateUtf8} from './privateFiles.js'
  4. import {parse} from './schema.js'
  5. import {HttpError} from '../errors.js'
  6. import type {IsolationGate} from './executor.js'
  7. const controls=['separate-identity','hermes-no-control-access','browser-private-no-cdp','restricted-egress'] as const
  8. const approval=z.object({version:z.literal(1),approved:z.literal(true),brokerUid:z.number().int().positive(),hermesUid:z.number().int().positive(),hermesPid:z.number().int().positive(),
  9. approvedAt:z.number().int().positive(),expiresAt:z.number().int().positive(),controls:z.array(z.enum(controls)).length(4)}).strict()
  10. const processUid=(pid:number)=>Number(execFileSync('/bin/ps',['-o','uid=','-p',String(pid)],{encoding:'utf8',timeout:2000,env:{PATH:'/usr/bin:/bin'}}).trim())
  11. /** Manual operator approval is a deployment trust contract, NOT proof of OS,
  12. * IPC/CDP/firewall isolation. Also checks the observable process identity.
  13. * CLI uses the real probe; tests inject a dummy probe without inspecting users. */
  14. export class OperatorIsolationGate implements IsolationGate {
  15. constructor(private path:string,private hermesUid:number,private hermesPid:number,private probe=processUid,private now=Date.now){}
  16. assert(){
  17. try{
  18. const a=parse(approval,JSON.parse(readPrivateUtf8(this.path,8192))),uid=process.getuid?.()
  19. if(!uid||uid!==a.brokerUid||uid===a.hermesUid||a.hermesUid!==this.hermesUid||a.hermesPid!==this.hermesPid||this.probe(a.hermesPid)!==a.hermesUid
  20. ||a.approvedAt>this.now()||a.expiresAt<=this.now()||a.expiresAt-a.approvedAt>31*86400000||new Set(a.controls).size!==controls.length)throw new Error()
  21. }catch{throw new HttpError(503,'执行隔离尚未获得有效人工批准或身份已变化','vault_isolation_required')}
  22. }
  23. }