mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-12 11:43:25 +08:00
Emergency (fallback / sealed-box) admins lost the workspace sidebar and saw "sign-in failed / add API key" on DSH Desktop because the remote.mux opens anonymously at boot and every early call was rejected terminally. - gateway: anonymous workspace/follow reaches dsh-acl's empty baseline; anonymous streams are parked until the carrier aborts instead of failing; anonymous workspace/initializeDefault answers "nothing created" - request-auth: a dead UDS_SESSION cookie no longer shadows a valid WS ticket or bridge (sync + async variants used by all callers) - dsh-acl: /api/* waits (bounded 15s) for the gateway ACL during Host startup instead of 403, so the Desktop welcome read no longer fails - desktop-bootstrap: key/account projection is best-effort per reference - client: emit connection/reset after login so boot-time caches (settings describe mirror -> Settings > Models) re-read under the real principal - sessions: absolute lifetime fixed at 7 days (was min(ttl, 7d) = 30 min); bridge expiry slides with its session; MemoryStore persisted to sessions.json (bearer hash only, userData AES-256-GCM sealed) - README: session lifetime/persistence and DSH compatibility notes Also includes previously uncommitted uds-auth 0.3.x work in this tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2 KiB
JavaScript
65 lines
2 KiB
JavaScript
/**
|
|
* Phase-4 ops tests (SEC-23/25 fragments).
|
|
*/
|
|
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
import { Readable } from 'node:stream'
|
|
import { readBodyLimited, readJsonBodyLimited } from '../lib/utils/read-body.js'
|
|
import { RateLimiter } from '../lib/utils/rate-limit.js'
|
|
import { audit, redactValue } from '../lib/utils/audit-log.js'
|
|
|
|
function fakeReq(chunks) {
|
|
return Readable.from(chunks)
|
|
}
|
|
|
|
test('SEC-23: readBodyLimited rejects oversized payload', async () => {
|
|
const big = Buffer.alloc(1024, 0x61)
|
|
await assert.rejects(
|
|
() => readBodyLimited(fakeReq([big, big]), { maxBytes: 1500 }),
|
|
(err) => err.code === 'payload_too_large',
|
|
)
|
|
})
|
|
|
|
test('SEC-23: readJsonBodyLimited parses small JSON', async () => {
|
|
const req = fakeReq([Buffer.from('{"a":1}')])
|
|
assert.deepEqual(await readJsonBodyLimited(req, { maxBytes: 100 }), { a: 1 })
|
|
})
|
|
|
|
test('SEC-23: RateLimiter eventually blocks and cleans buckets', () => {
|
|
const lim = new RateLimiter({ windowMs: 60_000, max: 3 })
|
|
assert.equal(lim.allow('ip1'), true)
|
|
assert.equal(lim.allow('ip1'), true)
|
|
assert.equal(lim.allow('ip1'), true)
|
|
assert.equal(lim.allow('ip1'), false)
|
|
assert.equal(lim.allow('ip2'), true)
|
|
lim.dispose()
|
|
})
|
|
|
|
test('SEC-25: audit redacts secrets and returns structured row', () => {
|
|
const lines = []
|
|
const row = audit({
|
|
action: 'test',
|
|
decision: 'deny',
|
|
reasonCode: 'x',
|
|
actor: 'u1',
|
|
meta: { token: 'secret-value', nested: { password: 'p' }, ok: 'fine' },
|
|
}, {
|
|
logger: {
|
|
warn(s) { lines.push(s) },
|
|
info() {},
|
|
},
|
|
})
|
|
assert.equal(row.type, 'uds-auth.audit')
|
|
assert.equal(row.meta.token, '[redacted]')
|
|
assert.equal(row.meta.nested.password, '[redacted]')
|
|
assert.equal(row.meta.ok, 'fine')
|
|
assert.equal(lines.length, 1)
|
|
assert.equal(lines[0].includes('secret-value'), false)
|
|
})
|
|
|
|
test('SEC-25: redactValue truncates long strings', () => {
|
|
const long = 'x'.repeat(100)
|
|
const out = redactValue(long)
|
|
assert.ok(String(out).includes('…'))
|
|
assert.equal(String(out).includes(long), false)
|
|
})
|