100 lines
3.1 KiB
JavaScript
100 lines
3.1 KiB
JavaScript
/**
|
|
* Mirrors semantics in lib/agent-helpers.js (apropos matching, mutate paths, proc allowlist).
|
|
* Keep aligned when editing helpers.
|
|
*/
|
|
import test from 'brittle'
|
|
|
|
/** @type {readonly string[]} */
|
|
const PROC_ALLOW = [
|
|
'/proc/bare_os/metrics_live.json',
|
|
'/proc/bare_os/features',
|
|
'/proc/bare_os/features.json',
|
|
'/proc/bare_os/swarm.json',
|
|
'/proc/bare_os/capabilities.json',
|
|
'/proc/bare_os/swarm_connection_manager_status.json'
|
|
]
|
|
|
|
function pathAllowedMutate(p) {
|
|
const x = String(p || '').replace(/\\/g, '/')
|
|
if (!x.startsWith('/') || x.includes('..')) return false
|
|
return (
|
|
x.startsWith('/home/') ||
|
|
x.startsWith('/tmp/') ||
|
|
x === '/tmp' ||
|
|
x.startsWith('/root/') ||
|
|
x.startsWith('/mnt/')
|
|
)
|
|
}
|
|
|
|
function procAllowed(p) {
|
|
const x = String(p || '').replace(/\\/g, '/')
|
|
if (!x.startsWith('/') || x.includes('..')) return false
|
|
return PROC_ALLOW.includes(x)
|
|
}
|
|
|
|
function aproposHits(db, needle, maxHits) {
|
|
const n = String(needle || '').toLowerCase()
|
|
const max = Math.min(Math.max(Math.floor(maxHits) || 40, 1), 200)
|
|
if (!n || !db || typeof db !== 'object') return { lines: [], truncated: false }
|
|
const d = /** @type {Record<string, unknown>} */ (db)
|
|
const pages = Array.isArray(d.pages) ? d.pages : []
|
|
const apropos = Array.isArray(d.apropos) ? d.apropos : []
|
|
const seen = new Set()
|
|
/** @type {string[]} */
|
|
const lines = []
|
|
let truncated = false
|
|
for (const row of apropos) {
|
|
if (!row || typeof row !== 'object') continue
|
|
const kw = typeof row.kw === 'string' ? row.kw : ''
|
|
if (!kw.includes(n)) continue
|
|
const idx = row.pageRef
|
|
if (typeof idx !== 'number' || !pages[idx]) continue
|
|
if (seen.has(idx)) continue
|
|
seen.add(idx)
|
|
const p = /** @type {Record<string, unknown>} */ (pages[idx])
|
|
const name = typeof p.name === 'string' ? p.name : ''
|
|
const sec = typeof p.section === 'number' ? p.section : 0
|
|
const title = typeof p.title === 'string' ? p.title : ''
|
|
lines.push(name + '(' + sec + ') - ' + title)
|
|
if (lines.length >= max) {
|
|
truncated = true
|
|
break
|
|
}
|
|
}
|
|
lines.sort()
|
|
return { lines, truncated }
|
|
}
|
|
|
|
test('mutate paths allow guest areas only', async (t) => {
|
|
t.ok(pathAllowedMutate('/home/guest/x'))
|
|
t.ok(pathAllowedMutate('/tmp/a'))
|
|
t.absent(pathAllowedMutate('/bin/foo'))
|
|
t.absent(pathAllowedMutate('/etc/passwd'))
|
|
t.absent(pathAllowedMutate('/proc/x'))
|
|
})
|
|
|
|
test('proc allowlist exact paths', async (t) => {
|
|
t.ok(procAllowed('/proc/bare_os/features'))
|
|
t.ok(procAllowed('/proc/bare_os/swarm.json'))
|
|
t.ok(procAllowed('/proc/bare_os/swarm_connection_manager_status.json'))
|
|
t.absent(procAllowed('/proc/bare_os/other.json'))
|
|
t.absent(procAllowed('/proc/../proc/bare_os/swarm.json'))
|
|
})
|
|
|
|
test('apropos substring matches kw like man -k', async (t) => {
|
|
const db = {
|
|
pages: [
|
|
{ name: 'grep', section: 1, title: 'pattern search' },
|
|
{ name: 'true', section: 1, title: 'exit successfully' }
|
|
],
|
|
apropos: [
|
|
{ kw: 'grep', pageRef: 0 },
|
|
{ kw: 'pattern', pageRef: 0 },
|
|
{ kw: 'true', pageRef: 1 }
|
|
]
|
|
}
|
|
const r = aproposHits(db, 'pat', 10)
|
|
t.is(r.lines.length, 1)
|
|
t.ok(r.lines[0].startsWith('grep('))
|
|
})
|