breaking
CI / test (push) Successful in 9m56s

This commit is contained in:
Raven Scott
2026-07-10 20:36:11 -04:00
parent d6ce72af66
commit 0bb6ba1692
31 changed files with 10231 additions and 3772 deletions
+91
View File
@@ -0,0 +1,91 @@
/**
* Append-only local audit log for privileged RPC actions.
*
* Path: PEARDOCK_AUDIT_LOG (default: ./peardock-audit.log)
* Set PEARDOCK_AUDIT=0 to disable.
*/
import fs from 'fs'
import path from 'path'
import logger from '../utils/logger.js'
const ENABLED = process.env.PEARDOCK_AUDIT !== '0'
const LOG_PATH = process.env.PEARDOCK_AUDIT_LOG || path.join(process.cwd(), 'peardock-audit.log')
/** Methods that should always be audited when successful/attempted. */
const AUDIT_METHODS = new Set([
'removeContainer',
'killContainer',
'pruneContainers',
'pruneImages',
'pruneNetworks',
'pruneVolumes',
'removeImage',
'removeStack',
'deployStack',
'deployContainer',
'buildImage',
'registryLogin',
'archiveContainerPut',
'removeNetwork',
'removeVolume',
'updateContainer',
'duplicateContainer',
'commitContainer',
])
/**
* @param {object} entry
* @param {string} entry.method
* @param {string} [entry.peerId]
* @param {string} [entry.role]
* @param {boolean} [entry.ok]
* @param {string} [entry.error]
* @param {object} [entry.args]
*/
export function audit(entry) {
if (!ENABLED) return
if (!entry?.method) return
if (!AUDIT_METHODS.has(entry.method) && entry.force !== true) return
const line = JSON.stringify({
ts: new Date().toISOString(),
method: entry.method,
peerId: entry.peerId ? String(entry.peerId).slice(0, 16) : null,
role: entry.role || null,
ok: entry.ok !== false,
error: entry.error || null,
// Never log secrets; only allowlisted arg keys
args: sanitizeArgs(entry.args),
})
try {
fs.appendFileSync(LOG_PATH, line + '\n', { encoding: 'utf8' })
} catch (err) {
logger.warn('audit log write failed', { error: err.message })
}
}
/**
* @param {unknown} args
*/
function sanitizeArgs(args) {
if (!args || typeof args !== 'object') return null
const out = {}
const keys = ['id', 'name', 'stackName', 'image', 'operation', 'path', 'term', 'containerIds']
for (const k of keys) {
if (args[k] != null) {
if (k === 'containerIds' && Array.isArray(args[k])) {
out[k] = args[k].slice(0, 20).map((x) => String(x).slice(0, 12))
} else {
out[k] = String(args[k]).slice(0, 128)
}
}
}
return Object.keys(out).length ? out : null
}
export function shouldAudit(method) {
return ENABLED && AUDIT_METHODS.has(method)
}
export { AUDIT_METHODS, LOG_PATH }