From 0db300dce5b7f3ac990e002aa85dc0f663f188dd Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 16 Jul 2026 10:53:34 -0500 Subject: [PATCH] fix(fleet): close documentation safety gaps Co-Authored-By: Claude Opus 4.8 --- docs/fleet/how-to/start-stop-restart.md | 2 +- .../758-fleet-config-ia-closure.md | 2 +- .../src/fleet/fleet-documentation.spec.ts | 68 ++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/docs/fleet/how-to/start-stop-restart.md b/docs/fleet/how-to/start-stop-restart.md index 85f9d78..93cf90f 100644 --- a/docs/fleet/how-to/start-stop-restart.md +++ b/docs/fleet/how-to/start-stop-restart.md @@ -16,7 +16,7 @@ mosaic fleet doctor Start with `--dry-run`. It validates roster semantics, deterministic projections, private managed paths, exact holder ownership, and named-socket state without changing files or lifecycle state. Explicit `apply` and `reconcile` rebuild derived projections and enforce persisted roster state: enabled `running` agents may start, while stopped or disabled agents are not started. This guarantee does not extend to reboot/service activation yet; boot preservation remains an FCM-M3-002 hold. -`start`, `stop`, and `restart` are explicit one-shot exact-service actions. They do not persist a lifecycle change. Roster CRUD is the only way to change persisted desired state. +`start`, `stop`, and `restart` are explicit one-shot exact-service actions. They do not persist a lifecycle change. `update` preserves the agent's existing lifecycle, and no delivered operation changes durable lifecycle after creation. Every command prints JSON. Observation commands report drift without mutation; `verify` exits non-zero on ownership mismatch, unmanaged sessions, or drift. A failed apply that wrote some derived projections reports `projections: "incomplete"` with bounded recovery to regenerate from the roster. A lifecycle failure after projections reports incomplete lifecycle work; it is never represented as a rollback or no-op. diff --git a/docs/reports/documentation/758-fleet-config-ia-closure.md b/docs/reports/documentation/758-fleet-config-ia-closure.md index 889713d..1bc424f 100644 --- a/docs/reports/documentation/758-fleet-config-ia-closure.md +++ b/docs/reports/documentation/758-fleet-config-ia-closure.md @@ -24,7 +24,7 @@ | IA pages | Every path named by the M0 checklist exists and is linked from `docs/fleet/README.md`. | | Examples | `docs/fleet/examples/roster-v2.yaml` validates through production v2 compiler/shared resolver; shipped artifact dispositions validate through declared production readers. | | Links | Deterministic local Markdown link test covers the entire fleet book and sitemap, including local heading-fragment resolution. | -| Sensitive/example safety | Validator scans every fenced fleet-book example plus the canonical roster for sensitive-looking keys, common credential formats, privileged commands, arbitrary command override, and hardcoded Tess/Ultron identities; docs consistently state value-free diagnostics. | +| Sensitive/example safety | Validator scans backtick- and tilde-fenced fleet-book examples plus the canonical roster for sensitive-looking keys, common credential formats (including Anthropic, OpenAI project, and Stripe restricted keys), path-qualified privileged commands, package-manager/root commands, arbitrary command override, and hardcoded Tess/Ultron identities; findings report only file/block and violation kind, never matched values. | | Holds | `docs/reports/deferred/758-fleet-config-deferrals.md` records M3-002, M4-002, M5-002, compatibility, and repository-structure boundaries. | ## Documentation completion checklist diff --git a/packages/mosaic/src/fleet/fleet-documentation.spec.ts b/packages/mosaic/src/fleet/fleet-documentation.spec.ts index 14cd1cf..addfb1d 100644 --- a/packages/mosaic/src/fleet/fleet-documentation.spec.ts +++ b/packages/mosaic/src/fleet/fleet-documentation.spec.ts @@ -135,9 +135,9 @@ function exampleSafetyViolationKinds(source: string): string[] { const kinds = new Set(); const sensitiveKey = /(?:secret|token|password|credential|MOSAIC_AGENT_COMMAND)/i; const credentialFormat = - /(?:\bAKIA[0-9A-Z]{16}\b|\bAIza[0-9A-Za-z_-]{35}\b|\bgh[pousr]_[A-Za-z0-9]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b|\bglpat-[A-Za-z0-9_-]{20,}\b|\bnpm_[A-Za-z0-9]{20,}\b|\bsk_live_[A-Za-z0-9]{16,}\b|\bxox[baprs]-[A-Za-z0-9-]{10,}\b|\bBearer\s+[A-Za-z0-9._~+/=-]{16,}\b|\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b|-----BEGIN [A-Z ]*PRIVATE KEY-----|\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/:]+:[^\s/@]+@)/; + /(?:\bAKIA[0-9A-Z]{16}\b|\bAIza[0-9A-Za-z_-]{35}\b|\bgh[pousr]_[A-Za-z0-9]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}\b|\bglpat-[A-Za-z0-9_-]{20,}\b|\bnpm_[A-Za-z0-9]{20,}\b|\bsk-ant-(?:api\d{2}-)?[A-Za-z0-9_-]{20,}\b|\bsk-proj-[A-Za-z0-9_-]{20,}\b|\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b|\bxox[baprs]-[A-Za-z0-9-]{10,}\b|\bBearer\s+[A-Za-z0-9._~+/=-]{16,}\b|\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b|-----BEGIN [A-Z ]*PRIVATE KEY-----|\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/:]+:[^\s/@]+@)/; const privilegedCommand = - /(?:^|[\n;&|])\s*(?:[$#>]\s*)?(?:env\s+(?:[A-Za-z_][A-Za-z0-9_]*=[^\s]+\s+)*|command\s+)*(?:(?:sudo|doas|pkexec)\s+|su\s+(?:-|--command\b|-c\b)|(?:systemctl|service|mount|umount|reboot|shutdown|poweroff|halt|chown|chmod|useradd|usermod|groupadd|visudo)\s+)/m; + /(?:^|[\n;&|])\s*(?:[$#>]\s*)?(?:(?:\/(?:[A-Za-z0-9._+-]+\/)*?)?(?:env|command)\s+(?:(?:-{1,2}[^\s]+|[A-Za-z_][A-Za-z0-9_]*=[^\s]+)\s+)*?)*(?:\/(?:[A-Za-z0-9._+-]+\/)*?)?(?:(?:sudo|doas|pkexec)\s+|su\s+(?:root\b|-|--command\b|-c\b)|(?:systemctl|service|mount|umount|reboot|shutdown|poweroff|halt|chown|chmod|chroot|useradd|usermod|groupadd|visudo|apt|apt-get|apt-cache|dpkg|yum|dnf|rpm|apk|pacman|zypper|emerge|snap|flatpak|brew|nix-env)\s+)/m; if (sensitiveKey.test(source)) kinds.add('sensitive-key'); if (credentialFormat.test(source)) kinds.add('credential-format'); @@ -147,7 +147,35 @@ function exampleSafetyViolationKinds(source: string): string[] { } function fencedCodeBlocks(source: string): string[] { - return [...source.matchAll(/```[^\n]*\n(.*?)```/gs)].map((match): string => match[1] ?? ''); + const blocks: string[] = []; + let fence: { readonly marker: string; readonly length: number } | undefined; + let lines: string[] = []; + + for (const line of source.split('\n')) { + const openingRun = line.match(/^\s{0,3}(`{3,}|~{3,})[^\n]*$/)?.[1]; + if (fence === undefined && openingRun !== undefined) { + fence = { marker: openingRun[0] ?? '', length: openingRun.length }; + lines = []; + continue; + } + if (fence === undefined) continue; + + const closingRun = line.match(/^\s{0,3}(`{3,}|~{3,})\s*$/)?.[1]; + if ( + closingRun !== undefined && + closingRun[0] === fence.marker && + closingRun.length >= fence.length + ) { + blocks.push(lines.join('\n')); + fence = undefined; + lines = []; + continue; + } + lines.push(line); + } + + if (fence !== undefined) blocks.push(lines.join('\n')); + return blocks; } const UNSAFE_EXAMPLE_FIXTURES = [ @@ -155,7 +183,27 @@ const UNSAFE_EXAMPLE_FIXTURES = [ expected: 'privileged-command', source: ['cd /srv && su', 'do systemctl restart example'].join(''), }, + { expected: 'privileged-command', source: '/usr/bin/sudo systemctl restart example' }, + { expected: 'privileged-command', source: '/usr/bin/apt-get install example' }, + { + expected: 'privileged-command', + source: '/usr/bin/env -i /usr/bin/command -- /usr/bin/apt-get install example', + }, + { expected: 'privileged-command', source: 'su root' }, + { expected: 'privileged-command', source: '/usr/sbin/chroot /srv/example' }, { expected: 'credential-format', source: ['ghp_', 'a'.repeat(36)].join('') }, + { + expected: 'credential-format', + source: ['sk-ant-api03-', 'a'.repeat(80)].join(''), + }, + { + expected: 'credential-format', + source: ['sk-proj-', 'a'.repeat(80)].join(''), + }, + { + expected: 'credential-format', + source: ['rk_live_', 'a'.repeat(24)].join(''), + }, { expected: 'credential-format', source: ['eyJ', 'a'.repeat(12), '.', 'b'.repeat(12), '.', 'c'.repeat(12)].join(''), @@ -198,6 +246,20 @@ describe('documentation validation regressions', (): void => { expect(exampleSafetyViolationKinds(source)).toContain(expected); }, ); + + it('extracts tilde-fenced and unclosed examples without exposing their contents', (): void => { + const sensitiveFixture = ['sk-ant-api03-', 'a'.repeat(80)].join(''); + const blocks = fencedCodeBlocks( + `~~~sh\n${sensitiveFixture}\n~~~\n\n\`\`\`sh\n${sensitiveFixture}`, + ); + + expect(blocks).toHaveLength(2); + for (const block of blocks) { + const kinds = exampleSafetyViolationKinds(block); + expect(kinds).toContain('credential-format'); + expect(JSON.stringify(kinds)).not.toContain(sensitiveFixture); + } + }); }); describe('fleet operator documentation', (): void => {