From 7656c78c72a6093cb6c275de0374265f947afe6b Mon Sep 17 00:00:00 2001 From: stacknil Date: Sun, 9 Aug 2026 11:51:51 +0800 Subject: [PATCH 1/3] fix(patterns): use UTC review provenance clock --- scripts/check_pattern_library.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/check_pattern_library.py b/scripts/check_pattern_library.py index ed721de..2ce869f 100644 --- a/scripts/check_pattern_library.py +++ b/scripts/check_pattern_library.py @@ -4,8 +4,9 @@ import json import re import sys +from collections.abc import Callable from dataclasses import dataclass -from datetime import date +from datetime import date, datetime, timezone from pathlib import Path from urllib.parse import unquote, urlparse @@ -17,6 +18,7 @@ FRONT_MATTER_RE = re.compile(r"\A---\n(.*?)\n---\n", re.DOTALL) HEADING_RE = re.compile(r"^## (.+)$", re.MULTILINE) LINK_RE = re.compile(r"\[[^\]]+\]\(([^)]+)\)") +DateClock = Callable[[], date] @dataclass(frozen=True) @@ -35,11 +37,18 @@ def load_config() -> dict[str, object]: return json.loads(CONFIG_PATH.read_text(encoding="utf-8")) +def utc_today() -> date: + """Return the UTC calendar date used by local and CI provenance checks.""" + return datetime.now(timezone.utc).date() + + def parse_card( path: Path, required_sections: list[str], maturity_values: set[str], errors: list[str], + *, + clock: DateClock = utc_today, ) -> Card | None: text = path.read_text(encoding="utf-8").replace("\r\n", "\n") match = FRONT_MATTER_RE.match(text) @@ -62,7 +71,7 @@ def parse_card( except (TypeError, ValueError): errors.append(f"{relative(path)}: last_reviewed must use YYYY-MM-DD") return None - if reviewed > date.today(): + if reviewed > clock(): errors.append(f"{relative(path)}: last_reviewed must not be in the future") return None @@ -112,7 +121,7 @@ def is_core_project_link(target: str, prefixes: list[str]) -> bool: return any(target == prefix or target.startswith(f"{prefix}/") for prefix in prefixes) -def validate() -> tuple[list[str], int, int, int]: +def validate(*, clock: DateClock = utc_today) -> tuple[list[str], int, int, int]: config = load_config() required_sections = list(config["required_sections"]) maturity_values = set(config["maturity_values"]) @@ -135,6 +144,7 @@ def validate() -> tuple[list[str], int, int, int]: required_sections, maturity_values, errors, + clock=clock, ) ) is not None From 5c4dbb85f6425337723e536342c9b0c22e6de62b Mon Sep 17 00:00:00 2001 From: stacknil Date: Sun, 9 Aug 2026 11:51:59 +0800 Subject: [PATCH 2/3] test(patterns): cover injected UTC review boundaries --- tests/test_validation_contracts.py | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/test_validation_contracts.py b/tests/test_validation_contracts.py index e56bd90..9df0a3b 100644 --- a/tests/test_validation_contracts.py +++ b/tests/test_validation_contracts.py @@ -3,6 +3,7 @@ import json import sys import unittest +from datetime import date from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import patch @@ -56,7 +57,7 @@ def pattern_card_text( class PatternLibraryContractTests(unittest.TestCase): - def test_parse_card_future_review_date_is_rejected(self) -> None: + def test_parse_card_future_review_date_is_rejected_against_utc_clock(self) -> None: with TemporaryDirectory() as temp_dir: temp_root = Path(temp_dir) card_path = temp_root / "patterns" / "future-review.md" @@ -64,7 +65,7 @@ def test_parse_card_future_review_date_is_rejected(self) -> None: card_path.write_text( pattern_card_text( title="Future review", - last_reviewed="2999-01-01", + last_reviewed="2026-07-16", ), encoding="utf-8", ) @@ -76,6 +77,7 @@ def test_parse_card_future_review_date_is_rejected(self) -> None: REQUIRED_PATTERN_SECTIONS, {"draft", "reviewed", "stable"}, errors, + clock=lambda: date(2026, 7, 15), ) self.assertIsNone(card) @@ -87,6 +89,32 @@ def test_parse_card_future_review_date_is_rejected(self) -> None: ], ) + def test_parse_card_review_date_on_utc_boundary_is_allowed(self) -> None: + with TemporaryDirectory() as temp_dir: + temp_root = Path(temp_dir) + card_path = temp_root / "patterns" / "boundary-review.md" + card_path.parent.mkdir(parents=True) + card_path.write_text( + pattern_card_text( + title="Boundary review", + last_reviewed="2026-07-16", + ), + encoding="utf-8", + ) + errors: list[str] = [] + + with patch.object(check_pattern_library, "ROOT", temp_root): + card = check_pattern_library.parse_card( + card_path, + REQUIRED_PATTERN_SECTIONS, + {"draft", "reviewed", "stable"}, + errors, + clock=lambda: date(2026, 7, 16), + ) + + self.assertIsNotNone(card) + self.assertEqual(errors, []) + def test_validate_stable_card_without_core_project_is_rejected(self) -> None: with TemporaryDirectory() as temp_dir: temp_root = Path(temp_dir) From 602d919e49c603488af3d636593afeb2c702b04c Mon Sep 17 00:00:00 2001 From: stacknil Date: Sun, 9 Aug 2026 11:52:19 +0800 Subject: [PATCH 3/3] docs(governance): record repo-sentinel baseline review --- docs/README.md | 1 + docs/repo-sentinel-baseline-review.md | 82 +++++++++++++++++++++++++++ docs/reviewer-brief.md | 3 + patterns/README.md | 7 ++- 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 docs/repo-sentinel-baseline-review.md diff --git a/docs/README.md b/docs/README.md index 9b5c4c5..38b2d05 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,6 +17,7 @@ This folder contains the governance and maintenance documents that keep the publ - [publication-workflow.md](publication-workflow.md): how to move from private/raw notes to public sanitized notes - [taxonomy-closure.md](taxonomy-closure.md): canonical taxonomy state and future workflow - [placeholder-closure.md](placeholder-closure.md): canonical placeholder state and checker workflow +- [repo-sentinel-baseline-review.md](repo-sentinel-baseline-review.md): redacted baseline classification and remote-gate decision record - [maintenance-checkpoint.md](maintenance-checkpoint.md): current markdownlint maintenance mode, operator commands, and audit baseline - [maintenance-quick-reference.md](maintenance-quick-reference.md): shortest-path maintainer commands by change type diff --git a/docs/repo-sentinel-baseline-review.md b/docs/repo-sentinel-baseline-review.md new file mode 100644 index 0000000..643b6fb --- /dev/null +++ b/docs/repo-sentinel-baseline-review.md @@ -0,0 +1,82 @@ +# Repo-Sentinel Baseline Review + +## Status + +This is a classification record for the current consumer baseline. The +committed `.reposentinel-baseline.json` is unchanged, and no remote +`repo-sentinel` gate is enabled by this review. + +The review keeps raw token values out of repository history, issues, and +reviewer-facing output. + +## Audit Scope + +The consumer snapshot is `sec-writeups-public` `main` at `9a18c74`. The +baseline is schema version `1`, generated at `2026-04-02T18:57:41Z`, and +contains 306 entries across 127 files. + +The candidate audit used `repo-sentinel-lite` commit `8a6e064` from the +v0.8 development line. It is recorded as an immutable audit input, not as a +released dependency or a claim that the remote gate is ready. + +Reproduction command: + +```bash +repo-sentinel baseline audit \ + --format json \ + --baseline .reposentinel-baseline.json \ + . +``` + +## Classification + +| Audit class | Count | Classification | Decision | +| --- | ---: | --- | --- | +| Active `secret.high_entropy` | 274 | Reviewed documentation, path, and lab-example suppressions | Retain; do not regenerate automatically | +| Active `repo.required_file_missing` | 1 | Missing `LICENSE`; repository governance condition, not a secret false positive | Keep unresolved and do not call the baseline fully approved | +| Relocated | 5 | Existing README content moved to new lines | Retain the suppression; review the movement, do not rewrite automatically | +| Ambiguous | 26 | Duplicate path/link content in README and workflow files | Manually inspected as false positives; preserve audit visibility | +| Stale | 0 | No baseline entries disappeared in this audit | No removals required | +| Unmatched | 2,567 | Classified below by evidence type | Do not add to the committed baseline in this pass | + +### Unmatched findings + +| Evidence class | Count | Decision | +| --- | ---: | --- | +| Generated report artifacts under `reports/` | 2,414 | Fixture/artifact; keep as audit evidence and do not suppress through a bulk refresh | +| Assignment-context examples | 22 | Fixture; keep the educational command examples visible | +| Command placeholders | 19 | Fixture; preserve the teaching syntax and review through placeholder policy | +| Repository paths and documentation metadata | 81 | False positive; no credential claim is made from path-like text | +| Context-reviewed CI, documentation, and pattern-link metadata | 18 | False positive; repeated names and links are repository structure | +| Context-reviewed lab paths, flags, and example values | 13 | Fixture; values are challenge or lab examples, not production credentials | + +The unmatched classification totals 2,567. No real credential is confirmed by +this review. A scan for common AWS, GitHub, OpenAI, Slack, private-key, JWT, +and long-hex marker formats found no matches; that heuristic does not replace +human review of future high-entropy findings. + +## Governance Decision + +The current baseline is useful as a reviewed suppression record, but it is not +ready to become a blocking remote gate yet. + +1. Keep the existing baseline unchanged until the missing `LICENSE` decision is + resolved and the reviewed suppression boundary is explicit. +2. Keep baseline audit output non-blocking. The changed-file policy should fail + on new error findings while baseline drift remains an independent review + signal. +3. Consume a reviewed `repo-sentinel` release or pin a reviewed immutable + commit before enabling the remote job. +4. Add the synthetic pass/fail/redaction integration test in the consumer + workflow before making the check required. +5. Preserve the rollback path: remove the remote job while retaining the local + pre-push hook. + +## Relationship To Issue #5 + +This record advances [issue #5](https://github.com/stacknil/sec-writeups-public/issues/5) +without claiming that the acceptance criteria are complete. The historical +issue snapshot and this v0.8 candidate audit are not directly comparable: +scanner rule coverage and baseline identity semantics changed between the two +runs. Future comparisons should always record the exact `repo-sentinel` +release or commit used for the audit. diff --git a/docs/reviewer-brief.md b/docs/reviewer-brief.md index f0ba56e..cc3a0a5 100644 --- a/docs/reviewer-brief.md +++ b/docs/reviewer-brief.md @@ -19,6 +19,7 @@ security source-note repository with: - Reproducible command: `python scripts/check_pattern_library.py` - Deterministic outputs: pattern maturity, provenance counts, project links, case-study backlinks, rendered README snapshots, and generated tag docs. +- Provenance boundary: `last_reviewed` is compared with the UTC calendar date and tested with an injected clock. - Tests / CI: pattern-contract validation, publication checks, placeholder checks, markdown checks, pre-commit hooks, and GitHub Actions workflows. - Release evidence: stable pattern index, source-note links, governance docs, sanitization checklist, and maintenance checkpoints. - Non-goals: raw exploit logs, private evidence dumps, live target identifiers, weaponized exploit chains, or unsanitized challenge transcripts. @@ -72,3 +73,5 @@ notes independently reviewable. Promote reviewed cards only when new evidence or implementation work increases their decision value; avoid growing the source archive as an end in itself. +The next governance case is the reviewed `repo-sentinel` baseline; its audit +evidence is recorded separately before any remote gate becomes blocking. diff --git a/patterns/README.md b/patterns/README.md index 2c00155..05b152c 100644 --- a/patterns/README.md +++ b/patterns/README.md @@ -39,8 +39,11 @@ These cards remain useful but are not part of the featured stable set: | `stable` | Evidence-bounded, linked to a core implementation, and supported by at least one source note. | Every card records `maturity` and `last_reviewed` in front matter. The review -date must be a valid current or historical date; future provenance claims fail -validation. +date is evaluated against the UTC calendar date +(`datetime.now(timezone.utc).date()`), so local timezone differences cannot +make a same-day review look like future provenance. Future provenance claims +fail validation, and the contract tests inject the clock rather than depending +on the machine's current date. ## Card Contract