feat(door): P2 connections + SSRF-safe executor + native adapter + audit

Migration 035 adds door_connections (per-space controller integration:
secret_ref -> AES-256-GCM secrets vault, never a plaintext password;
pinned_host SSRF pin; native_heatsync|generic_http adapter;
allow_member_self_entry opt-in for phase 3) and door_access_log
(append-only, secrets redacted, service-client-only writes; immutable).
RLS additive/default-deny: connections CRUD = door.manage; log SELECT =
door.manage/operate.

Safety core is pure and heavily unit-tested (door-logic, suite 409):
validateDoorTarget (request host must equal the pinned host;
metadata/link-local always blocked; http(s) only), the
firmware-verified HeatSync encoders with fixed-width zero-padding
(slot 3 / perm 3 / tag 8 / 4-char password), generic template
substitution, and redactDoorSecrets. lib/door/executor.ts is the single
hardened egress: re-validates the pin, refuses redirects, caps time and
body, redacts before any audit write. The door password is read from the
secrets vault and decrypted server-side only, never returned or logged.
/door/manage (door.manage) configures connections, picks a vault secret,
and runs a safe status-only test. Sidebar Admin link added; "Learn"
moved before "People" per request. Docs updated.
This commit is contained in:
Moheeb Zara
2026-05-16 20:31:43 -07:00
parent 1cbea138ba
commit 59e9fcbd4f
17 changed files with 1203 additions and 11 deletions
+93 -1
View File
@@ -1,5 +1,15 @@
import { describe, it, expect } from 'vitest'
import { last4, maskCardUid } from '@/lib/door-logic'
import {
last4,
maskCardUid,
isAlwaysBlockedHost,
validateDoorTarget,
encodeHeatSyncGrant,
encodeHeatSyncRevoke,
encodeHeatSyncControl,
applyTemplate,
redactDoorSecrets,
} from '@/lib/door-logic'
describe('last4', () => {
it('returns the last 4 chars, or the whole short value', () => {
@@ -20,3 +30,85 @@ describe('maskCardUid', () => {
expect(maskCardUid('')).toBe('••••')
})
})
describe('isAlwaysBlockedHost', () => {
it('blocks cloud metadata and link-local', () => {
expect(isAlwaysBlockedHost('169.254.169.254')).toBe(true)
expect(isAlwaysBlockedHost('169.254.1.2')).toBe(true)
expect(isAlwaysBlockedHost('metadata.google.internal')).toBe(true)
expect(isAlwaysBlockedHost('fe80::1')).toBe(true)
expect(isAlwaysBlockedHost('0.0.0.0')).toBe(true)
})
it('allows ordinary LAN hosts', () => {
expect(isAlwaysBlockedHost('192.168.1.50')).toBe(false)
expect(isAlwaysBlockedHost('10.0.0.5')).toBe(false)
expect(isAlwaysBlockedHost('door.local')).toBe(false)
})
})
describe('validateDoorTarget', () => {
it('allows a LAN target that matches the pin', () => {
const r = validateDoorTarget('http://192.168.1.50/?o1&e=1234', '192.168.1.50')
expect(r.ok).toBe(true)
})
it('rejects a host that does not match the pin (anti-redirect/SSRF)', () => {
const r = validateDoorTarget('http://evil.example.com/x', '192.168.1.50')
expect(r.ok).toBe(false)
})
it('rejects metadata even if someone pins it', () => {
const r = validateDoorTarget('http://169.254.169.254/latest/meta-data/', '169.254.169.254')
expect(r.ok).toBe(false)
})
it('rejects non-http protocols', () => {
expect(validateDoorTarget('file:///etc/passwd', 'x').ok).toBe(false)
expect(validateDoorTarget('gopher://192.168.1.50', '192.168.1.50').ok).toBe(false)
})
it('pin may be entered with scheme/port/path and still matches host', () => {
const r = validateDoorTarget('http://192.168.1.50:8080/?9&e=1234', 'http://192.168.1.50:8080/door')
expect(r.ok).toBe(true)
})
})
describe('encodeHeatSyncGrant', () => {
it('zero-pads slot/perm to 3 and tag to 8 (firmware byte offsets)', () => {
const r = encodeHeatSyncGrant({ slot: 7, permissionMask: 1, tagHex: 'abcd', password: '1234' })
expect(r).toEqual({ ok: true, query: '?m007&p001&t0000abcd&e=1234' })
})
it('rejects out-of-range slot/perm and bad tag', () => {
expect(encodeHeatSyncGrant({ slot: 999, permissionMask: 1, tagHex: 'ab', password: '1' }).ok).toBe(false)
expect(encodeHeatSyncGrant({ slot: 1, permissionMask: 500, tagHex: 'ab', password: '1' }).ok).toBe(false)
expect(encodeHeatSyncGrant({ slot: 1, permissionMask: 1, tagHex: 'xyz', password: '1' }).ok).toBe(false)
expect(encodeHeatSyncGrant({ slot: 1, permissionMask: 1, tagHex: 'ab', password: '' }).ok).toBe(false)
})
})
describe('encodeHeatSyncRevoke / control', () => {
it('revoke zero-pads the slot and appends auth', () => {
expect(encodeHeatSyncRevoke(12, 'pass')).toEqual({ ok: true, query: '?r012&e=pass' })
})
it('control maps verbs and appends auth', () => {
expect(encodeHeatSyncControl('open1', 'pw')).toEqual({ ok: true, query: '?o1&e=pw' })
expect(encodeHeatSyncControl('status', 'pw')).toEqual({ ok: true, query: '?9&e=pw' })
expect(encodeHeatSyncControl('nope', 'pw').ok).toBe(false)
})
})
describe('applyTemplate', () => {
it('substitutes and URL-encodes known vars, leaves unknowns', () => {
expect(applyTemplate('?card={tag}&slot={slot}', { tag: 'a b', slot: 5 })).toBe('?card=a%20b&slot=5')
expect(applyTemplate('?x={missing}', {})).toBe('?x={missing}')
})
})
describe('redactDoorSecrets', () => {
it('scrubs the literal password and e= query value', () => {
expect(redactDoorSecrets('GET http://x/?m007&t00ab&e=1234 -> 200', '1234'))
.toBe('GET http://x/?m007&t00ab&e=<redacted> -> 200')
})
it('scrubs auth-ish params even without the password known', () => {
expect(redactDoorSecrets('?pw=hunter2&token=abc')).toBe('?pw=<redacted>&token=<redacted>')
})
it('leaves unrelated text intact', () => {
expect(redactDoorSecrets('status 200 ok', null)).toBe('status 200 ok')
})
})