73 lines
1.9 KiB
JavaScript
73 lines
1.9 KiB
JavaScript
'use strict'
|
||
|
||
/**
|
||
* Secret redaction helpers (agent/SECURITY.md S4).
|
||
* Invites, caps, seeds must never appear unredacted in progress logs.
|
||
*/
|
||
|
||
// z32 alphabet includes 1 and 8 (not only 2–7)
|
||
const Z32_CHARS = 'a-z0-9'
|
||
const INVITE_RE = new RegExp(`\\bfj1\\.[${Z32_CHARS}]+`, 'gi')
|
||
// Long z32 blobs (keys/caps) — avoid short words
|
||
const Z32_LONG_RE = new RegExp(`\\b[${Z32_CHARS}]{40,}\\b`, 'gi')
|
||
|
||
/**
|
||
* Redact a full fj1. invite string for logs.
|
||
* @param {string} invite
|
||
* @returns {string}
|
||
*/
|
||
function redactInvite(invite) {
|
||
const s = String(invite || '')
|
||
if (!s.startsWith('fj1.') || s.length < 12) return s ? '[redacted-invite]' : ''
|
||
const body = s.slice(4)
|
||
if (body.length <= 12) return 'fj1.' + '*'.repeat(Math.min(8, body.length))
|
||
return `fj1.${body.slice(0, 6)}…${body.slice(-4)} (len=${s.length})`
|
||
}
|
||
|
||
/**
|
||
* Redact a z32 key/cap for logs.
|
||
* @param {string} key
|
||
* @returns {string}
|
||
*/
|
||
function redactKey(key) {
|
||
const s = String(key || '')
|
||
if (s.length < 8) return s ? '[redacted-key]' : ''
|
||
return `${s.slice(0, 4)}…${s.slice(-4)} (len=${s.length})`
|
||
}
|
||
|
||
/**
|
||
* Redact any fj1. tokens (and long z32 blobs) inside a free-form message.
|
||
* @param {string} text
|
||
* @returns {string}
|
||
*/
|
||
function redactSecretsInText(text) {
|
||
let s = String(text || '')
|
||
s = s.replace(INVITE_RE, (m) => redactInvite(m))
|
||
// Avoid mangling short words; only long z32-looking tokens
|
||
s = s.replace(Z32_LONG_RE, (m) => {
|
||
if (m.startsWith('fj1')) return m
|
||
return redactKey(m)
|
||
})
|
||
return s
|
||
}
|
||
|
||
/**
|
||
* True if string looks like an invite or long secret.
|
||
* @param {string} s
|
||
* @returns {boolean}
|
||
*/
|
||
function looksLikeSecret(s) {
|
||
const t = String(s || '')
|
||
if (t.startsWith('fj1.')) return true
|
||
if (/^[a-z2-7]{40,}$/i.test(t)) return true
|
||
if (/seed|private.?key|cap=/i.test(t) && t.length > 20) return true
|
||
return false
|
||
}
|
||
|
||
module.exports = {
|
||
redactInvite,
|
||
redactKey,
|
||
redactSecretsInText,
|
||
looksLikeSecret
|
||
}
|