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"
|
||||
},
|
||||
"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": {
|
||||
"status": "completed",
|
||||
"date": "2026-02-25",
|
||||
|
||||
@@ -97,4 +97,19 @@ describe('phase0BaselineArtifactRetention', () => {
|
||||
it('rejects negative keep limit', () => {
|
||||
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)) {
|
||||
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);
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user