Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/actions.lock
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ workflows:
'.github/workflows/scorecard.yml':
- 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66'
'.github/workflows/secret-scanner.yml':
- 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66'
- 'hyperpolymath/cicd-suite@4404c3c0474e17753fe7689f3485c02890aac113'
'.github/workflows/supervised-fleet-scan.yml':
- 'actions/checkout@v7.0.1'
'.github/workflows/vendored-bot-drift.yml':
Expand Down Expand Up @@ -173,6 +173,13 @@ dependencies:
commit: 'sha1-0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d'
owner_id: 75048950
repo_id: 623796603
'hyperpolymath/cicd-suite@4404c3c0474e17753fe7689f3485c02890aac113':
ref: '4404c3c0474e17753fe7689f3485c02890aac113'
commit: 'sha1-4404c3c0474e17753fe7689f3485c02890aac113'
owner_id: 6759885
repo_id: 1326697643
uses:
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
'hyperpolymath/deed-ecosystem@main':
ref: 'main'
commit: 'sha1-ed83d6927e8fb21431e403dbf6d7a4af96772746'
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ jobs:
- uses: actions/checkout@v7.0.1
- name: Install PyYAML (for workflow YAML validation)
run: pip install pyyaml --quiet
- name: Test workflow lock-map and regeneration audit
run: |
python3 tests/workflow-lock-map-test.py
python3 tests/lock-regeneration-test.py
python3 scripts/audit-workflow-lock-map.py
- name: Verify automation quarantine
run: bash tests/automation-quarantine-test.sh
- name: Verify fix-outcome verdicts
Expand Down
32 changes: 24 additions & 8 deletions .github/workflows/secret-scanner.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
# This workflow is managed by gh actions-lock.
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# This workflow is managed by gh actions-lock.
name: Secret Scanner

# Consumer migration to the published cicd-suite scanner. The three contexts
# this repository's rulesets require — scan / gitleaks, scan / rust-secrets,
# scan / shell-secrets — are produced by the callee's gitleaks job and by its
# supplemental-results matrix; none is renamed, added or dropped here.
#
# Migration boundaries:
# * `fast-pr` stays false: non-PR runs scan full history, and introduced-commit
# PR scanning is not enabled before the security owner accepts it.
# * No `secrets:` line. The callee declares no workflow_call secret and the
# scanner installs its own checksum-pinned binary, so `secrets: inherit`
# would forward every repository and organisation secret for no benefit.
# * `contents: read` is the whole grant; the previous caller's
# security-events / pull-requests / actions grants are not carried over.
# * Triggers are unchanged: pull_request and push to main, nothing else.

on:
pull_request:
push:
Expand All @@ -14,15 +27,18 @@ concurrency:
cancel-in-progress: true

permissions:
actions: read
contents: read

jobs:
scan:
# This job-level block REPLACES the workflow-level one. `contents: read` is
# the complete grant the callee's jobs declare; granting more is needless
# authority, granting less rejects the run at startup with zero jobs.
permissions:
security-events: write
contents: read
pull-requests: write
actions: read
uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@75bd5994470661a099df562d6d5ad2fe744dbd66
# secret-scanner-reusable declares no caller secret, so none is passed.
uses: hyperpolymath/cicd-suite/.github/workflows/secret-scanner-reusable.yml@4404c3c0474e17753fe7689f3485c02890aac113
with:
# The canary passed, but introduced-commit PR coverage is not approved.
# Every run continues to scan full history.
fast-pr: false
# Deliberately no `secrets:` line — see the boundary note above.
25 changes: 25 additions & 0 deletions audits/workflow-lock-map-2026-10-31/REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Fleet workflow lock-map investigation

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.

## Observed coverage

* 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.
* 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.
* 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.
* 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.
* 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.
* 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.
* 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.

## Regression coverage added before any further lock edits

`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.

`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.

## Not run / decision gate

* 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.
* 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.
* 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.
119 changes: 119 additions & 0 deletions scripts/audit-lock-regeneration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
"""Compare reviewed closure against regeneration; does not discover remote closure."""
import re
import sys
import yaml


class UniqueLoader(yaml.SafeLoader):
pass


def unique_mapping(loader, node, deep=False):
"""Construct a YAML mapping, raising ValueError for duplicate keys.

Pass deep through to PyYAML's key and value construction. YAML construction
errors and TypeError for unhashable keys propagate to the caller.
"""
mapping = {}
for key_node, value_node in node.value:
item = loader.construct_object(key_node, deep=deep)
if item in mapping:
raise ValueError(f'duplicate YAML key: {item!r}')
mapping[item] = loader.construct_object(value_node, deep=deep)
return mapping


UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, unique_mapping)


def key(value):
"""Normalize an owner/repository@ref key, preserving the ref's case.

Raise ValueError if the reference is not in the supported form.
"""
if not isinstance(value, str) or not re.fullmatch(r'[^\s/@]+/[^\s/@]+@[^\s@]+', value):
raise ValueError(f'invalid dependency reference: {value!r}')
repo, ref = value.split('@')
return f'{repo.lower()}@{ref}'


def load(path):

Check failure on line 42 in scripts/audit-lock-regeneration.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 27 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_gitbot-fleet&issues=AaGTJnUC7JDpWu2qIaPI&open=AaGTJnUC7JDpWu2qIaPI&pullRequest=632
"""Load and validate a lock file as (dependencies, workflow roots).

Dependency keys and edges are normalized; each dependency value contains
its ref, commit, and set of edges. Raise ValueError for invalid records,
references, or missing targets. File, YAML, and key construction errors
propagate to the caller.
"""
with open(path, encoding='utf-8') as f:

Check failure on line 50 in scripts/audit-lock-regeneration.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Path Traversal via command-line argument

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_gitbot-fleet&issues=AaGTJnUC7JDpWu2qIaPJ&open=AaGTJnUC7JDpWu2qIaPJ&pullRequest=632
doc = yaml.load(f, Loader=UniqueLoader)
if not isinstance(doc, dict) or not isinstance(doc.get('workflows'), dict) or not isinstance(doc.get('dependencies'), dict):
raise ValueError(f'{path}: expected workflows and dependencies mappings')
deps = {}
for name, record in doc['dependencies'].items():
k = key(name)
if k in deps or not isinstance(record, dict):
raise ValueError(f'{path}: duplicate or invalid record {name}')
ref, commit = record.get('ref'), record.get('commit')
if not isinstance(ref, str) or not re.fullmatch(r'[^\s@]+', ref) or ref.startswith('sha1-'):
raise ValueError(f'{path}: invalid ref for {name}')
if not isinstance(commit, str) or not re.fullmatch(r'sha1-[0-9a-f]{40}', commit):
raise ValueError(f'{path}: invalid commit for {name}')
named_ref = name.split('@')[1]
if re.fullmatch(r'[0-9a-f]{40}', named_ref):
if commit != 'sha1-' + named_ref:
raise ValueError(f'{path}: SHA key and commit differ for {name}')
elif named_ref != ref:
raise ValueError(f'{path}: tag ref differs from key for {name}')
edges = record.get('uses', [])
if not isinstance(edges, list):
raise ValueError(f'{path}: invalid uses for {name}')
deps[k] = (ref, commit, {key(edge) for edge in edges})
roots = set()
for workflow, edges in doc['workflows'].items():
if not isinstance(workflow, str) or not isinstance(edges, list):
raise ValueError(f'{path}: invalid workflow {workflow!r}')
roots.update(key(edge) for edge in edges)
for edge in roots | set().union(*(r[2] for r in deps.values())):
if edge not in deps:
raise ValueError(f'{path}: missing dependency {edge}')
return deps, roots


def audit(old_path, new_path):
"""Compare reviewed dependencies reachable from proposed workflow roots.

For roots present in the reviewed lock, check that their reviewed
reachable records, refs, commits, and edges remain in the proposed lock.
Print a success summary; new sources still require independent review.
Raise ValueError on a mismatch and propagate lock loading errors.
"""
old, _ = load(old_path)
new, roots = load(new_path)
queue = list(roots & old.keys())
seen = set()
while queue:
k = queue.pop()
if k in seen:
continue
seen.add(k)
if k not in new:
raise ValueError(f'dropped reviewed dependency {k}')
if old[k][:2] != new[k][:2]:
raise ValueError(f'changed reviewed ref/commit {k}')
missing = old[k][2] - new[k][2]
if missing:
raise ValueError(f'dropped reviewed edges from {k}: {sorted(missing)}')
queue.extend(old[k][2])
print(f'Preserved reviewed reachable closure ({len(seen)} records); new sources require independent review.')


if __name__ == '__main__':
if len(sys.argv) != 3:
sys.exit(f'usage: {sys.argv[0]} REVIEWED_LOCK PROPOSED_LOCK')
try:
audit(*sys.argv[1:])
except (OSError, ValueError, yaml.YAMLError) as exc:
sys.exit(f'lock regeneration audit: {exc}')
Loading
Loading