fix(audit): reject malformed rolling artifact tags
Harden phase0 rolling retention timestamp parsing with explicit bounds and UTC round-trip validation; add regression coverage for invalid date/time tags. No architecture/protocol flow changes; diagram files reviewed and no updates were needed.
This commit is contained in:
@@ -439,6 +439,18 @@
|
|||||||
],
|
],
|
||||||
"test_status": "pnpm test:run src/audit/phase0BaselineScriptWiring.test.ts + pnpm typecheck passing"
|
"test_status": "pnpm test:run src/audit/phase0BaselineScriptWiring.test.ts + pnpm typecheck passing"
|
||||||
},
|
},
|
||||||
|
"phase0-live-baseline-rolling-tag-validation-hardening": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-27",
|
||||||
|
"updated": "2026-02-27",
|
||||||
|
"summary": "Hardened rolling artifact retention tag parsing to reject impossible timestamp components (month/day/time bounds and invalid calendar dates) so malformed filenames cannot be misclassified through date normalization.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/audit/phase0BaselineArtifactRetention.ts",
|
||||||
|
"src/audit/phase0BaselineArtifactRetention.test.ts",
|
||||||
|
"docs/plans/state.json"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm test:run src/audit/phase0BaselineArtifactRetention.test.ts + pnpm typecheck passing"
|
||||||
|
},
|
||||||
"phase0-instrumentation-ticket-checklist": {
|
"phase0-instrumentation-ticket-checklist": {
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"date": "2026-02-25",
|
"date": "2026-02-25",
|
||||||
|
|||||||
@@ -97,4 +97,19 @@ describe('phase0BaselineArtifactRetention', () => {
|
|||||||
it('rejects negative keep limit', () => {
|
it('rejects negative keep limit', () => {
|
||||||
expect(() => planRollingPhase0ArtifactRetention([], -1)).toThrow('keepPerFamily');
|
expect(() => planRollingPhase0ArtifactRetention([], -1)).toThrow('keepPerFamily');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('ignores malformed rolling tags with impossible date or time values', () => {
|
||||||
|
const rows = collectRollingPhase0ArtifactFiles([
|
||||||
|
'phase0_baseline_live_2026-13-27-010203.json',
|
||||||
|
'phase0_baseline_live_gateway_2026-02-30-010203.json',
|
||||||
|
'phase0_baseline_live_backend_pi_embedded_2026-02-27-246001.json',
|
||||||
|
'phase0_baseline_live_backend_native_2026-02-27-016061.json',
|
||||||
|
'phase0_baseline_live_backend_drift_2026-02-27-010203.md',
|
||||||
|
'phase0_baseline_live_prune_2026-00-27-010203.json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(rows).toHaveLength(1);
|
||||||
|
expect(rows[0]?.file_name).toBe('phase0_baseline_live_backend_drift_2026-02-27-010203.md');
|
||||||
|
expect(rows[0]?.family).toBe('backend_drift');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,9 +74,28 @@ function parseRollingTagTimestampMs(tag: string): number | undefined {
|
|||||||
if (!Number.isFinite(hour) || !Number.isFinite(minute) || !Number.isFinite(second)) {
|
if (!Number.isFinite(hour) || !Number.isFinite(minute) || !Number.isFinite(second)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
if (month < 1 || month > 12 || day < 1 || day > 31 || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const timestampMs = Date.UTC(year, month - 1, day, hour, minute, second);
|
const timestampMs = Date.UTC(year, month - 1, day, hour, minute, second);
|
||||||
return Number.isFinite(timestampMs) ? timestampMs : undefined;
|
if (!Number.isFinite(timestampMs)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const utc = new Date(timestampMs);
|
||||||
|
if (
|
||||||
|
utc.getUTCFullYear() !== year
|
||||||
|
|| utc.getUTCMonth() !== month - 1
|
||||||
|
|| utc.getUTCDate() !== day
|
||||||
|
|| utc.getUTCHours() !== hour
|
||||||
|
|| utc.getUTCMinutes() !== minute
|
||||||
|
|| utc.getUTCSeconds() !== second
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timestampMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRollingArtifactFile(fileName: string): Phase0RollingArtifactFile | undefined {
|
function parseRollingArtifactFile(fileName: string): Phase0RollingArtifactFile | undefined {
|
||||||
|
|||||||
Reference in New Issue
Block a user