Skip to content

Commit 51298a9

Browse files
hyperpolymatharena-agentcoderabbitai[bot]
authored
ci: migrate fleet secret scanner to bounded suite (#632)
## Scope Migrate only the existing Secret Scanner caller to published cicd-suite commit 4404c3c0474e17753fe7689f3485c02890aac113. Keep all three required contexts, existing triggers, full-history scanning (`fast-pr: false`), contents-read permissions and no inherited secrets. Preserve the standards lock record for the other callers. The new suite record includes checkout at the immutable SHA used in the pinned reusable; identity was checked with the GitHub repository API. ## Evidence and hold The first consumer canary unified-dataset-vocab PR #81 merged as 4921ac0d161d5619c3186b2e916ed3f6e2c8c89f; its PR run 41198606402 and main run 41199623322 passed real controls, full scanning and all three SHA-bound contexts. This PR must independently demonstrate actual jobs, setup, detector controls and scanner contexts at its own head, and must not merge on corpus-only evidence. Lock regeneration and deeper closure safeguards are the separate fleet PR #631, still open. Coordinate its reviewed receipts/closure audit with this change before merge; neither `gh actions-lock --verify-local` nor a green dangling-edge check proves source-derived closure. Here the existing-lock comparison fixtures pass; authoritative CLI regeneration/provenance checks and the local gawk gate were not run because their binaries are unavailable in the sandbox. Review the observed GitHub runner behaviour and CI evidence; stop on zero-job runs, missing contexts or incomplete closure. Do not bypass policy. Rollback: revert this PR, restoring caller and lock together, and verify actual jobs and all required checks; the previous scanner had zero-job startup failures, so rollback alone does not prove recovery. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Chores** * Updated the automated secret-scanning workflow to use a pinned scanner version with read-only repository access. * **Tests** * Added automated checks for workflow lock-map coverage and lock regeneration, and included them in the end-to-end workflow. * Added coverage for missing workflow and dependency records, invalid references, and changes to reviewed dependencies. * **Documentation** * Added a report summarizing workflow lock-map coverage and review findings. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Arena Agent <297053741+arena-agent@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 83e35bc commit 51298a9

8 files changed

Lines changed: 710 additions & 9 deletions

File tree

‎.github/workflows/actions.lock‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ workflows:
6565
'.github/workflows/scorecard.yml':
6666
- 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66'
6767
'.github/workflows/secret-scanner.yml':
68-
- 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66'
68+
- 'hyperpolymath/cicd-suite@4404c3c0474e17753fe7689f3485c02890aac113'
6969
'.github/workflows/supervised-fleet-scan.yml':
7070
- 'actions/checkout@v7.0.1'
7171
'.github/workflows/vendored-bot-drift.yml':
@@ -173,6 +173,13 @@ dependencies:
173173
commit: 'sha1-0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d'
174174
owner_id: 75048950
175175
repo_id: 623796603
176+
'hyperpolymath/cicd-suite@4404c3c0474e17753fe7689f3485c02890aac113':
177+
ref: '4404c3c0474e17753fe7689f3485c02890aac113'
178+
commit: 'sha1-4404c3c0474e17753fe7689f3485c02890aac113'
179+
owner_id: 6759885
180+
repo_id: 1326697643
181+
uses:
182+
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
176183
'hyperpolymath/deed-ecosystem@main':
177184
ref: 'main'
178185
commit: 'sha1-ed83d6927e8fb21431e403dbf6d7a4af96772746'

‎.github/workflows/e2e.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ jobs:
2020
- uses: actions/checkout@v7.0.1
2121
- name: Install PyYAML (for workflow YAML validation)
2222
run: pip install pyyaml --quiet
23+
- name: Test workflow lock-map and regeneration audit
24+
run: |
25+
python3 tests/workflow-lock-map-test.py
26+
python3 tests/lock-regeneration-test.py
27+
python3 scripts/audit-workflow-lock-map.py
2328
- name: Verify automation quarantine
2429
run: bash tests/automation-quarantine-test.sh
2530
- name: Verify fix-outcome verdicts
Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1-
# This workflow is managed by gh actions-lock.
21
# SPDX-License-Identifier: MPL-2.0
32
# This workflow is managed by gh actions-lock.
4-
# This workflow is managed by gh actions-lock.
53
name: Secret Scanner
64

5+
# Consumer migration to the published cicd-suite scanner. The three contexts
6+
# this repository's rulesets require — scan / gitleaks, scan / rust-secrets,
7+
# scan / shell-secrets — are produced by the callee's gitleaks job and by its
8+
# supplemental-results matrix; none is renamed, added or dropped here.
9+
#
10+
# Migration boundaries:
11+
# * `fast-pr` stays false: non-PR runs scan full history, and introduced-commit
12+
# PR scanning is not enabled before the security owner accepts it.
13+
# * No `secrets:` line. The callee declares no workflow_call secret and the
14+
# scanner installs its own checksum-pinned binary, so `secrets: inherit`
15+
# would forward every repository and organisation secret for no benefit.
16+
# * `contents: read` is the whole grant; the previous caller's
17+
# security-events / pull-requests / actions grants are not carried over.
18+
# * Triggers are unchanged: pull_request and push to main, nothing else.
19+
720
on:
821
pull_request:
922
push:
@@ -14,15 +27,18 @@ concurrency:
1427
cancel-in-progress: true
1528

1629
permissions:
17-
actions: read
1830
contents: read
1931

2032
jobs:
2133
scan:
34+
# This job-level block REPLACES the workflow-level one. `contents: read` is
35+
# the complete grant the callee's jobs declare; granting more is needless
36+
# authority, granting less rejects the run at startup with zero jobs.
2237
permissions:
23-
security-events: write
2438
contents: read
25-
pull-requests: write
26-
actions: read
27-
uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66
28-
# secret-scanner-reusable declares no caller secret, so none is passed.
39+
uses: hyperpolymath/cicd-suite/.github/workflows/secret-scanner-reusable.yml@4404c3c0474e17753fe7689f3485c02890aac113
40+
with:
41+
# The canary passed, but introduced-commit PR coverage is not approved.
42+
# Every run continues to scan full history.
43+
fast-pr: false
44+
# Deliberately no `secrets:` line — see the boundary note above.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Fleet workflow lock-map investigation
2+
3+
Scope: this repository, branch candidate `f8d11228f62abd8784f249995966a449a65c796b` (scanner migration PR #632), compared with main `83e35bcfd04d43960d635fec5397da8dfa344268`. This is not a catalogue or an estate certification. No lock was changed by this investigation.
4+
5+
## Observed coverage
6+
7+
* All **26** current `.github/workflows/*.yml` workflow files have keys in `actions.lock`, both at main and at the candidate. A key is only map coverage, not proof of a runnable workflow or complete closure. There is no observed unmapped workflow to classify as intentionally unmanaged; no owner-approved exception list was supplied.
8+
* Three entries are explicitly empty: `label-triage.yml`, `labels.yml` and `lock-sync-gate.yml`. The workflow YAML contains no `uses` in these files. Their comments describe an intentional no-action design; these are **mapped zero-use workflows**, not missing lock-map entries or evidence of an unmanaged exemption. An empty mapping for a workflow that actually has an action or remote reusable call is a finding.
9+
* The other 23 workflow files have direct external refs and/or a local reusable call; the inventory finds all their directly named external refs in the appropriate workflow map and records for all mapped refs. The local call in `fleet-report-back.yml` points to `fleet-report-back-reusable.yml`, whose own lock entry names its checkout, cache and artifact actions. The local caller's map does not separately list all callee actions. That is a distinction requiring runner/tool semantics, not by itself proof of missing closure. Main run [40933665680](https://github.com/hyperpolymath/gitbot-fleet/actions/runs/40933665680) succeeded; it does not prove every ref or event path was exercised.
10+
* The four remaining standards job-level calls in the candidate are mapped to the existing standards record. The migrated scanner is mapped to a cicd-suite record. Current `gh-actions-lock` [documents](https://github.com/github/gh-actions-lock#limitations) that remote job-level reusable calls are not locked by the tool. Do not infer complete remote closure from the caller map or CLI local verification.
11+
* On this candidate, the pinned scanner source at `hyperpolymath/cicd-suite@4404c3c0474e17753fe7689f3485c02890aac113`, `.github/workflows/secret-scanner-reusable.yml`, was fetched from GitHub and its external `uses` set is `actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1`, matching the candidate's suite record and an existing dependency record. The [PR scanner run](https://github.com/hyperpolymath/gitbot-fleet/actions/runs/41227361766) at that candidate created jobs and passed the controls, scan and three contexts. It is not a post-merge run.
12+
* At their **locked commits**, direct composite sources for `deed-ecosystem/validate-action`, `k9-ecosystem/validate-action`, and `smtp-notify-action` have no nested `uses`; `actions/upload-pages-artifact` names `actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f`, which is listed in its record and has a record of its own. These observations do not establish full recursive closure for all other actions, other workflows, or all inputs.
13+
* The earlier fleet report's **4 of 14** map observation (M13/F7) was about the published **cicd-suite** repository, not this repository. Its report separately identifies real regeneration risks here (F2/F3). Neither that observation nor this inventory proves a missing workflow key in fleet.
14+
15+
## Regression coverage added before any further lock edits
16+
17+
`tests/workflow-lock-map-test.py` covers a mapped zero-use workflow, an unexpected missing map, an explicit unmanaged exception (reported as unverified, exit 2), typo exceptions, missing direct action and job-level reusable edges, missing nested records, local reusable coverage at its own path, malformed YAML/ref handling, and an extra workflow edge requiring review. `scripts/audit-workflow-lock-map.py` checks the actual map; it emits findings separately from source-review notes. Its successful exit means only that these map-level checks passed. It cannot detect an edge and its target removed together, discover remote or local composite closure, validate commit/repository identities, or decide whether an extra entry is necessary.
18+
19+
`tests/lock-regeneration-test.py` and `scripts/audit-lock-regeneration.py` separately test comparison to a previously reviewed graph, including dropped composite/reusable edges with no dangling target and malformed ref representations. That comparison cannot establish that the old graph was complete, nor can it review a new pin; it is not an authoritative regeneration or provenance check. Fixtures and the map inventory run in E2E, which already installs PyYAML. They do **not** automatically compare each PR's new lock to the base; PR #631 proposes separate SHA-bound source receipts and broader audit coverage and is still open.
20+
21+
## Not run / decision gate
22+
23+
* Official `gh actions-lock` regeneration and provenance verification were not run: its release asset was inaccessible from this sandbox. No generated lock diff is claimed.
24+
* No complete, recursive source inventory and provenance verification for **all** transitive dependencies was performed; no owner-confirmed intentionally unmanaged workflows were identified. The possibility of a missing closure outside the specific sources above remains open.
25+
* Before any further lock regeneration, review source-derived SHA-bound closure (including local composites and reusable workflow subpaths), run the separate dropped-closure checks with a reviewed baseline, use the official tooling where available, and validate actual runner startup. Do not rewrite pinned tag refs or immutable resolutions merely to make a check pass. Coordinate with PR #631 rather than treating this map inventory or the catalogue as a fix.

‎scripts/audit-lock-regeneration.py‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python3
2+
# SPDX-License-Identifier: MPL-2.0
3+
"""Compare reviewed closure against regeneration; does not discover remote closure."""
4+
import re
5+
import sys
6+
import yaml
7+
8+
9+
class UniqueLoader(yaml.SafeLoader):
10+
pass
11+
12+
13+
def unique_mapping(loader, node, deep=False):
14+
"""Construct a YAML mapping, raising ValueError for duplicate keys.
15+
16+
Pass deep through to PyYAML's key and value construction. YAML construction
17+
errors and TypeError for unhashable keys propagate to the caller.
18+
"""
19+
mapping = {}
20+
for key_node, value_node in node.value:
21+
item = loader.construct_object(key_node, deep=deep)
22+
if item in mapping:
23+
raise ValueError(f'duplicate YAML key: {item!r}')
24+
mapping[item] = loader.construct_object(value_node, deep=deep)
25+
return mapping
26+
27+
28+
UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, unique_mapping)
29+
30+
31+
def key(value):
32+
"""Normalize an owner/repository@ref key, preserving the ref's case.
33+
34+
Raise ValueError if the reference is not in the supported form.
35+
"""
36+
if not isinstance(value, str) or not re.fullmatch(r'[^\s/@]+/[^\s/@]+@[^\s@]+', value):
37+
raise ValueError(f'invalid dependency reference: {value!r}')
38+
repo, ref = value.split('@')
39+
return f'{repo.lower()}@{ref}'
40+
41+
42+
def load(path):
43+
"""Load and validate a lock file as (dependencies, workflow roots).
44+
45+
Dependency keys and edges are normalized; each dependency value contains
46+
its ref, commit, and set of edges. Raise ValueError for invalid records,
47+
references, or missing targets. File, YAML, and key construction errors
48+
propagate to the caller.
49+
"""
50+
with open(path, encoding='utf-8') as f:
51+
doc = yaml.load(f, Loader=UniqueLoader)
52+
if not isinstance(doc, dict) or not isinstance(doc.get('workflows'), dict) or not isinstance(doc.get('dependencies'), dict):
53+
raise ValueError(f'{path}: expected workflows and dependencies mappings')
54+
deps = {}
55+
for name, record in doc['dependencies'].items():
56+
k = key(name)
57+
if k in deps or not isinstance(record, dict):
58+
raise ValueError(f'{path}: duplicate or invalid record {name}')
59+
ref, commit = record.get('ref'), record.get('commit')
60+
if not isinstance(ref, str) or not re.fullmatch(r'[^\s@]+', ref) or ref.startswith('sha1-'):
61+
raise ValueError(f'{path}: invalid ref for {name}')
62+
if not isinstance(commit, str) or not re.fullmatch(r'sha1-[0-9a-f]{40}', commit):
63+
raise ValueError(f'{path}: invalid commit for {name}')
64+
named_ref = name.split('@')[1]
65+
if re.fullmatch(r'[0-9a-f]{40}', named_ref):
66+
if commit != 'sha1-' + named_ref:
67+
raise ValueError(f'{path}: SHA key and commit differ for {name}')
68+
elif named_ref != ref:
69+
raise ValueError(f'{path}: tag ref differs from key for {name}')
70+
edges = record.get('uses', [])
71+
if not isinstance(edges, list):
72+
raise ValueError(f'{path}: invalid uses for {name}')
73+
deps[k] = (ref, commit, {key(edge) for edge in edges})
74+
roots = set()
75+
for workflow, edges in doc['workflows'].items():
76+
if not isinstance(workflow, str) or not isinstance(edges, list):
77+
raise ValueError(f'{path}: invalid workflow {workflow!r}')
78+
roots.update(key(edge) for edge in edges)
79+
for edge in roots | set().union(*(r[2] for r in deps.values())):
80+
if edge not in deps:
81+
raise ValueError(f'{path}: missing dependency {edge}')
82+
return deps, roots
83+
84+
85+
def audit(old_path, new_path):
86+
"""Compare reviewed dependencies reachable from proposed workflow roots.
87+
88+
For roots present in the reviewed lock, check that their reviewed
89+
reachable records, refs, commits, and edges remain in the proposed lock.
90+
Print a success summary; new sources still require independent review.
91+
Raise ValueError on a mismatch and propagate lock loading errors.
92+
"""
93+
old, _ = load(old_path)
94+
new, roots = load(new_path)
95+
queue = list(roots & old.keys())
96+
seen = set()
97+
while queue:
98+
k = queue.pop()
99+
if k in seen:
100+
continue
101+
seen.add(k)
102+
if k not in new:
103+
raise ValueError(f'dropped reviewed dependency {k}')
104+
if old[k][:2] != new[k][:2]:
105+
raise ValueError(f'changed reviewed ref/commit {k}')
106+
missing = old[k][2] - new[k][2]
107+
if missing:
108+
raise ValueError(f'dropped reviewed edges from {k}: {sorted(missing)}')
109+
queue.extend(old[k][2])
110+
print(f'Preserved reviewed reachable closure ({len(seen)} records); new sources require independent review.')
111+
112+
113+
if __name__ == '__main__':
114+
if len(sys.argv) != 3:
115+
sys.exit(f'usage: {sys.argv[0]} REVIEWED_LOCK PROPOSED_LOCK')
116+
try:
117+
audit(*sys.argv[1:])
118+
except (OSError, ValueError, yaml.YAMLError) as exc:
119+
sys.exit(f'lock regeneration audit: {exc}')

0 commit comments

Comments
 (0)