Files
bare-operating-system/packages/bare-os-booter/lib/bare-os-peer-admission.js
T
Raven Scott 5647491b08 Implement BareOS Zero-Trust Boot and Runtime Hardening (plan batches A–E).
Peer admission and bootstrap controls
- Fail closed when BARE_OS_PEER_ALLOWLIST_HEX is empty unless explicit
  break-glass BARE_OS_PEER_ALLOW_ALL=1.
- Treat BARE_OS_ZERO_TRUST_PROFILE=strict|security like strict admission
  posture alongside BARE_OS_PEER_ALLOWLIST_STRICT.
- Document BARE_OS_PEER_ALLOW_ALL and profile semantics; update boot trust
  model operator guidance.

Peer system seed and provenance
- In strict/security profile, peer system seed defaults off unless
  BARE_OS_PEER_SYSTEM_SEED is explicitly enabled (1/true/yes).
- Disable synthetic capability filling in strict profile; keep compat path
  when profile is not strict.
- Extend test.peer-system-seed.js for strict default-off and no-synthesis.

Path capability signer trust
- When BARE_OS_PATH_CAPABILITY_ENFORCE_READ is on, require trusted issuer
  if BARE_OS_PATH_CAPABILITY_REQUIRE_TRUSTED_SIGNER is set or profile is
  strict; wire verifyPathCapabilityEnvelopeTrusted into the primary deny
  path.
- Document BARE_OS_PATH_CAPABILITY_REQUIRE_TRUSTED_SIGNER and trusted key
  list usage in environment appendix.

Host delegates (least privilege)
- Under strict/security profile, empty BARE_OS_DELEGATE_ALLOW means deny-all
  delegates instead of allow-all; document behavior.
- Add delegate strict-profile test coverage.

Audit durability and telemetry hygiene
- Retain audit chain rows in memory and add bareOsAuditPersistRows for
  optional NDJSON persistence via VFS.
- Broaden var-log redaction for secret-shaped strings and env-like assignments.
- Emit boot.log security line when unsafe trust combinations are detected.

Release and CI gates
- Add scripts/verify-zero-trust-gates.mjs and npm run verify:zero-trust-gates.
- Document verifier in scripts/README.md and zero-trust steps in
  docs/release-checklist.md.

Tests
- Update bare-os-booter admission tests for allow-all and empty-allowlist
  messaging.
- Relax brittle man.json page-count equality to a minimal sanity check to
  avoid brittle/os.cwd brittle failures on inventory drift.

Verification (local): npm run verify:zero-trust-gates; npm run test -w
bare-os-booter; peer-system-seed brittle lane as applicable.

Plan file (.cursor/plans/zero-trust-boot-runtime-100-plan_*.plan.md) was not
edited per instructions.
2026-04-26 23:49:54 -04:00

236 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Optional peer admission gate from **`BARE_OS_PEER_ALLOWLIST_HEX`**
* (comma-separated hex public keys; empty = deny unless explicit allow-all).
*
* **`BARE_OS_PEER_DENYLIST_HEX`** — comma-separated hex keys; if the peer matches,
* verdict is **`deny`** before allowlist evaluation (denylist wins).
*
* **`BARE_OS_PEER_REQUIRE_CAPS_JSON`** — JSON array of capability strings; when set,
* **`meta.caps`** must include every required token (hosts pass peer caps when known).
*
* When **`BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST`** is set and callers pass
* **`dhtAddressClass`** (hyperdht-addressstyle hint), the class must appear in
* the allow list after the key gate passes.
*
* Admission results use **`schema: 2`** (v1 was **`schema: 1`** only).
*
* @param {Record<string, unknown> | null | undefined} env
* @param {string} peerKeyHex
* @param {{ dhtAddressClass?: string, caps?: string[] } | null | undefined} [meta]
*/
const ADMISSION_SCHEMA = 2
/**
* @param {Record<string, unknown> | null | undefined} env
*/
function zeroTrustStrictProfile(env) {
const p = String(env?.BARE_OS_ZERO_TRUST_PROFILE || '')
.trim()
.toLowerCase()
return p === 'strict' || p === 'security'
}
/** @param {string} s */
function normKeyHex(s) {
return String(s || '')
.trim()
.toLowerCase()
.replace(/^0x/, '')
}
/** @param {string} raw */
function keySetFromCommaHex(raw) {
return new Set(
raw
.split(/[\s,]+/)
.map((s) => normKeyHex(s))
.filter(Boolean)
)
}
/**
* @param {Record<string, unknown> | null | undefined} env
* @returns {string[] | null}
*/
function parseRequireCaps(env) {
const raw = String(env?.BARE_OS_PEER_REQUIRE_CAPS_JSON || '').trim()
if (!raw) return null
try {
const j = JSON.parse(raw)
if (!Array.isArray(j)) return null
const caps = j
.map((x) => String(x).trim())
.filter(Boolean)
.slice(0, 32)
return caps.length ? caps : null
} catch {
return null
}
}
export function evaluateBareOsPeerAdmission(env, peerKeyHex, meta = undefined) {
const want = normKeyHex(peerKeyHex)
const atMs = () => Date.now()
const denyRaw = String(env?.BARE_OS_PEER_DENYLIST_HEX || '').trim()
if (denyRaw) {
const denySet = keySetFromCommaHex(denyRaw)
if (want && denySet.has(want)) {
return {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'peer_denylist',
denylistSize: denySet.size,
atMs: atMs()
}
}
}
const requireCaps = parseRequireCaps(env)
if (requireCaps && requireCaps.length > 0) {
const peerCapsRaw = meta && Array.isArray(meta.caps) ? meta.caps : []
const peerSet = new Set(
peerCapsRaw.map((c) => String(c).trim()).filter(Boolean)
)
for (const c of requireCaps) {
if (!peerSet.has(c)) {
return {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'peer_missing_cap',
requiredCaps: [...requireCaps],
atMs: atMs()
}
}
}
}
const raw = String(env?.BARE_OS_PEER_ALLOWLIST_HEX || '').trim()
const strict =
env?.BARE_OS_PEER_ALLOWLIST_STRICT === '1' ||
env?.BARE_OS_PEER_ALLOWLIST_STRICT === 'true' ||
zeroTrustStrictProfile(env)
const allowAll =
env?.BARE_OS_PEER_ALLOW_ALL === '1' || env?.BARE_OS_PEER_ALLOW_ALL === 'true'
/** @type {{ schema: number, verdict: string, note?: string, allowlistSize?: number, strictProfile?: boolean, atMs?: number, dhtAddressClassGate?: Record<string, unknown> }} */
let base
if (!raw) {
if (strict || !allowAll) {
base = {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'peer_allowlist_empty',
note:
'Peer allowlist is empty; admission denied unless BARE_OS_PEER_ALLOW_ALL=1.'
}
} else {
base = {
schema: ADMISSION_SCHEMA,
verdict: 'allow',
note: 'No allowlist and BARE_OS_PEER_ALLOW_ALL=1; peers admitted.'
}
}
} else {
const set = keySetFromCommaHex(raw)
const ok = want && set.has(want)
base = {
schema: ADMISSION_SCHEMA,
verdict: ok ? 'allow' : 'deny',
allowlistSize: set.size,
strictProfile: strict || undefined,
atMs: atMs()
}
}
const classRaw = String(env?.BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST || '').trim()
if (!classRaw) {
if (base.atMs == null) base.atMs = atMs()
return base
}
const allowClasses = new Set(
classRaw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)
)
if (allowClasses.size === 0) {
if (base.atMs == null) base.atMs = atMs()
return base
}
const cls = String(meta?.dhtAddressClass || '')
.trim()
.toLowerCase()
if (!cls) {
return {
...base,
atMs: base.atMs ?? atMs(),
dhtAddressClassGate: {
schema: 1,
mode: 'no_peer_class_hint',
note: 'BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST is set but caller did not supply dhtAddressClass — stock Hyperswarm path does not classify peers; allow key verdict only.'
}
}
}
if (!allowClasses.has(cls)) {
return {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'dht_address_class',
dhtAddressClass: cls,
allowedClasses: [...allowClasses],
note: 'Peer DHT address class not listed in BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST.',
atMs: atMs()
}
}
if (base.verdict === 'deny') return base
return {
...base,
atMs: base.atMs ?? atMs(),
dhtAddressClassGate: { schema: 1, mode: 'allow', class: cls }
}
}
/**
* Build an event-bus row for **`peer_admission`** audit (**never** includes full peer keys).
*
* @param {string} peerKeyHex
* @param {{ verdict: string, reason?: string, schema: number }} result
* @param {{ dhtAddressClass?: string } | null | undefined} [meta]
*/
export function bareOsFormatPeerAdmissionAuditEvent(peerKeyHex, result, meta) {
const norm = String(peerKeyHex || '')
.trim()
.toLowerCase()
.replace(/^0x/, '')
const keyShort = norm ? norm.slice(0, 16) : ''
return {
type: 'peer_admission',
verdict: result.verdict,
reason: result.reason,
peerKeyHexPrefix: keyShort || undefined,
admissionSchema: result.schema,
dhtAddressClass:
meta && typeof meta === 'object'
? String(meta.dhtAddressClass || '').trim() || undefined
: undefined
}
}
/**
* Per-bucket rate gate for admission audit (**mutates** **`lastMap`** on allow).
*
* @param {string} bucket
* @param {number} now
* @param {number} rateMs
* @param {Map<string, number>} lastMap
*/
export function bareOsPeerAdmissionAuditRateAllow(bucket, now, rateMs, lastMap) {
if (rateMs <= 0) return true
const last = lastMap.get(bucket) ?? 0
if (now - last < rateMs) return false
lastMap.set(bucket, now)
return true
}