Redact a bug before it is stored, and give it a report shape
Evidence was kept raw, so a key in a log line reached the page and the store; and there was no format to send even once it was clean.
This commit is contained in:
@@ -1239,6 +1239,22 @@ function vv_ai_bug_write(string $component, string $summary, string $evidence, a
|
||||
if ($component === '' || $summary === '' || $evidence === '') return ['ok' => false];
|
||||
if (!vv_ai_scope_ok($component)) return ['ok' => false];
|
||||
|
||||
// Redacted on the way in, not on the way out. A report is built from this record, shown on
|
||||
// the AI page from this record, and pasted into an issue from this record — redacting at any
|
||||
// one of those leaves the other two holding the secret, and the store keeps it forever.
|
||||
//
|
||||
// Counted so the report can say something was removed. Silent redaction reads as a short log,
|
||||
// and a reader who cannot tell the difference cannot judge what they are looking at.
|
||||
$summary = vv_ai_redact($summary);
|
||||
$evidence = vv_ai_redact($evidence);
|
||||
$redacted = substr_count($summary . "\0" . $evidence, VV_AI_REDACTED);
|
||||
foreach ($ctx as $k => $v) {
|
||||
if (is_string($v)) {
|
||||
$ctx[$k] = vv_ai_redact($v);
|
||||
$redacted += substr_count($ctx[$k], VV_AI_REDACTED);
|
||||
}
|
||||
}
|
||||
|
||||
// The commit as it was when the fault was seen, not when the report is read. "Is this already
|
||||
// fixed" is the first question any maintainer asks, and a version captured later answers a
|
||||
// different question. Slot id (host1/host2) rather than the hostname — this text is written
|
||||
@@ -1262,6 +1278,10 @@ function vv_ai_bug_write(string $component, string $summary, string $evidence, a
|
||||
'seen' => 1,
|
||||
'open' => true,
|
||||
'context' => array_slice($ctx, 0, 12),
|
||||
'redacted' => $redacted,
|
||||
// The shape this record was written in. A report format will change; being able to read
|
||||
// an old record without guessing which fields it has is what makes that survivable.
|
||||
'template' => 1,
|
||||
];
|
||||
$p = vv_ai_bug_file($rec);
|
||||
if (is_file($p)) {
|
||||
@@ -1277,6 +1297,79 @@ function vv_ai_bug_write(string $component, string $summary, string $evidence, a
|
||||
return ['ok' => true, 'id' => $rec['id'], 'seen' => $rec['seen']];
|
||||
}
|
||||
|
||||
// Renders one stored bug as the report that gets pasted into an issue.
|
||||
//
|
||||
// The shape is built around one rule: observed fact and inference are separated and labelled.
|
||||
// A report that blends "the log said X" with "this is probably a permissions problem" is one that
|
||||
// the next reader — a person, or a model asked to look at it — will quote back as evidence. Every
|
||||
// heading below is either quoted output or explicitly marked as a reading of it.
|
||||
//
|
||||
// Written to be pasted somewhere public, so it carries the host slot rather than the hostname and
|
||||
// nothing that identifies the machine. What it does carry is the two facts a maintainer asks for
|
||||
// first: which commit, and how many times.
|
||||
function vv_ai_bug_report(array $b): string {
|
||||
$g = fn(string $k, $d = '') => $b[$k] ?? $d;
|
||||
$ctx = (array) $g('context', []);
|
||||
$when = fn($t) => $t ? date('Y-m-d H:i', (int) $t) : '?';
|
||||
|
||||
$seen = (int) $g('seen', 1);
|
||||
$out = "## Varaverk bug report\n\n";
|
||||
$out .= "| | |\n|---|---|\n";
|
||||
$out .= '| **Summary** | ' . str_replace('|', '\\|', (string) $g('summary')) . " |\n";
|
||||
$out .= '| **Component** | `' . $g('component') . "` |\n";
|
||||
$out .= '| **Commit** | `' . ($g('commit') ?: 'unknown') . "` |\n";
|
||||
$out .= '| **Seen** | ' . $seen . '× · first ' . $when($g('first'))
|
||||
. ' · last ' . $when($g('last')) . " |\n";
|
||||
$out .= '| **Host slot** | ' . $g('host', '?') . " |\n";
|
||||
$out .= '| **Report id** | `' . $g('id', '?') . "` |\n";
|
||||
$out .= '| **Template** | ' . (int) $g('template', 1) . " |\n\n";
|
||||
|
||||
// Whether the quote below was checked against the log it claims to come from. The worker
|
||||
// refuses to file a bug whose evidence it could not find, so this is normally true — but a
|
||||
// report that asserts it without having checked is worth less than one that says it did not.
|
||||
$verified = $ctx['verified'] ?? null;
|
||||
$vLabel = $verified === true ? 'verified present in ' . ($ctx['log'] ?? 'the log')
|
||||
: ($verified === false ? 'NOT found in the log — treat with suspicion'
|
||||
: 'not checked against a log');
|
||||
$out .= "### Evidence — verbatim, " . $vLabel . "\n\n```\n"
|
||||
. rtrim((string) $g('evidence')) . "\n```\n\n";
|
||||
|
||||
if (!empty($ctx['asked'])) {
|
||||
$out .= "### What was being asked when it was noticed\n\n> "
|
||||
. str_replace("\n", "\n> ", trim((string) $ctx['asked'])) . "\n\n";
|
||||
}
|
||||
|
||||
$out .= "### Environment\n\n";
|
||||
$env = array_filter([
|
||||
'Unraid ' . (vv_ai_bug_unraid_version() ?: '?'),
|
||||
'PHP ' . PHP_VERSION,
|
||||
!empty($ctx['profile']) ? 'assistant profile: ' . $ctx['profile'] : null,
|
||||
!empty($ctx['scope']) ? 'scope: ' . $ctx['scope'] : null,
|
||||
]);
|
||||
$out .= '- ' . implode("\n- ", $env) . "\n\n";
|
||||
|
||||
// Stated even when nothing was removed, because "0 values" and no line at all read very
|
||||
// differently to someone deciding whether the log looks suspiciously short.
|
||||
$n = (int) $g('redacted', 0);
|
||||
$out .= "### Redaction\n\n";
|
||||
$out .= $n > 0
|
||||
? $n . ' value' . ($n === 1 ? '' : 's') . " matched a known credential or a secret-shaped "
|
||||
. "assignment and " . ($n === 1 ? 'was' : 'were') . " replaced with `" . VV_AI_REDACTED . "`.\n"
|
||||
: "Nothing matched a known credential. Note that redaction catches secrets held in this "
|
||||
. "install's conf, or written as an assignment — an unlabelled token printed by another "
|
||||
. "program would not be caught, so read the evidence before sending.\n";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
// Best effort, and blank rather than wrong. /etc/unraid-version is a shell assignment; on
|
||||
// anything that is not Unraid there is simply no file and the report says "?".
|
||||
function vv_ai_bug_unraid_version(): string {
|
||||
$raw = @file_get_contents('/etc/unraid-version');
|
||||
if (!is_string($raw)) return '';
|
||||
return preg_match('/"([^"]+)"/', $raw, $m) ? $m[1] : trim(explode('=', $raw, 2)[1] ?? '');
|
||||
}
|
||||
|
||||
function vv_ai_bugs_list(bool $openOnly = true): array {
|
||||
$out = [];
|
||||
foreach ((array)@glob(vv_ai_bugs_dir() . '/*.json') as $f) {
|
||||
|
||||
Reference in New Issue
Block a user