fix(fleet): close documentation safety gaps
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
| IA pages | Every path named by the M0 checklist exists and is linked from `docs/fleet/README.md`. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| Holds | `docs/reports/deferred/758-fleet-config-deferrals.md` records M3-002, M4-002, M5-002, compatibility, and repository-structure boundaries. |
|
||||||
|
|
||||||
## Documentation completion checklist
|
## Documentation completion checklist
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ function exampleSafetyViolationKinds(source: string): string[] {
|
|||||||
const kinds = new Set<string>();
|
const kinds = new Set<string>();
|
||||||
const sensitiveKey = /(?:secret|token|password|credential|MOSAIC_AGENT_COMMAND)/i;
|
const sensitiveKey = /(?:secret|token|password|credential|MOSAIC_AGENT_COMMAND)/i;
|
||||||
const credentialFormat =
|
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 =
|
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 (sensitiveKey.test(source)) kinds.add('sensitive-key');
|
||||||
if (credentialFormat.test(source)) kinds.add('credential-format');
|
if (credentialFormat.test(source)) kinds.add('credential-format');
|
||||||
@@ -147,7 +147,35 @@ function exampleSafetyViolationKinds(source: string): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fencedCodeBlocks(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 = [
|
const UNSAFE_EXAMPLE_FIXTURES = [
|
||||||
@@ -155,7 +183,27 @@ const UNSAFE_EXAMPLE_FIXTURES = [
|
|||||||
expected: 'privileged-command',
|
expected: 'privileged-command',
|
||||||
source: ['cd /srv && su', 'do systemctl restart example'].join(''),
|
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: ['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',
|
expected: 'credential-format',
|
||||||
source: ['eyJ', 'a'.repeat(12), '.', 'b'.repeat(12), '.', 'c'.repeat(12)].join(''),
|
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);
|
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 => {
|
describe('fleet operator documentation', (): void => {
|
||||||
|
|||||||
Reference in New Issue
Block a user