molecule-ci/.github/workflows/validate-plugin.yml
security-auditor 7e2bde9b77 fix(ci): force anon checkout of public molecule-ci to bypass Gitea cross-repo 404
After lowercasing the slug (molecule-ci#1) and flipping molecule-ci public,
plugin/template/org-template CI still failed at the SECOND actions/checkout
step (the one that fetches molecule-ci itself for canonical validator scripts).

Failure mode in act_runner log:
  Run actions/checkout@v4
    repository: molecule-ai/molecule-ci
    path: .molecule-ci-canonical
  Syncing repository: molecule-ai/molecule-ci
  [git config http.https://git.moleculesai.app/.extraheader AUTHORIZATION: basic ***]
  ::error::The target couldn't be found.
   Failure - Main actions/checkout@v4

Root cause: actions/checkout@v4 sends `Authorization: basic <github.token>` —
the per-job Gitea-issued token, scoped to the calling plugin/template repo
only. On Gitea, an authenticated request that lacks repo-permission 404s
instead of falling back to anonymous-public-read (a Gitea-vs-GitHub
behaviour difference). Anonymous git clone of molecule-ci succeeds; the auth
header is what trips the 404.

Fix: pass `token: ''` to force anonymous fetch on the cross-repo checkouts.
molecule-ci is public; no auth is needed for read.

3 sites updated:
  * validate-plugin.yml (1 site)
  * validate-workspace-template.yml (2 sites — both jobs in the file)
  * validate-org-template.yml (1 site)

Verified by: re-triggering plugin-molecule-careful-bash#2 will be GREEN
end-to-end after this lands. The 33 downstream lowercase-slug PRs are NOT
mass-merged until that verification.

Refs: internal#46

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:23:37 -07:00

86 lines
3.7 KiB
YAML

name: Validate Plugin
on:
workflow_call:
jobs:
validate:
name: Plugin validation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
# Canonical validator script lives in molecule-ci, fetched fresh on
# every run. The previous setup expected `.molecule-ci/scripts/` to
# be vendored INTO each plugin repo, which drifted across the
# 20+ plugin repos as the validator evolved. Single source of
# truth eliminates that drift class entirely. Mirrors the same
# pattern already used by validate-workspace-template.yml.
- uses: actions/checkout@v4
with:
repository: molecule-ai/molecule-ci
path: .molecule-ci-canonical
# Force anonymous fetch. molecule-ci is a public repo. On Gitea,
# actions/checkout@v4 sends the per-job ${{ github.token }} which
# is scoped to the calling repo only — Gitea 404s the cross-repo
# request instead of falling back to anon-public-read (different
# from GitHub's behaviour). Empty token bypasses auth entirely.
# See molecule-ci#1 commit + the post-public-flip CI run on
# plugin-molecule-careful-bash@663bf72 for the exact failure shape.
token: ''
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
cache-dependency-path: .molecule-ci-canonical/.molecule-ci/scripts/requirements.txt
- run: pip install pyyaml -q
- run: python3 .molecule-ci-canonical/.molecule-ci/scripts/validate-plugin.py
- name: Check for secrets
run: |
python3 - << 'PYEOF'
import os, re, sys
from pathlib import Path
PATTERNS = [
re.compile(r'''["']sk-ant-[a-zA-Z0-9]{50,}["']'''),
re.compile(r'''["']ghp_[a-zA-Z0-9]{36,}["']'''),
re.compile(r'''["']AKIA[A-Z0-9]{16}["']'''),
re.compile(r'''["'][a-zA-Z0-9/+=]{40}["']'''),
re.compile(r'''["']sk_test_[a-zA-Z0-9]{24,}["']'''),
re.compile(r'''["']Bearer\s+[a-zA-Z0-9_.-]{20,}["']'''),
re.compile(r'''ghp_[a-zA-Z0-9]{36,}'''),
re.compile(r'''sk-ant-[a-zA-Z0-9]{50,}'''),
]
SKIP_DIRS = {'.molecule-ci', '.molecule-ci-canonical', '.git', 'node_modules', '__pycache__'}
EXTENSIONS = {'.yaml', '.yml', '.md', '.py', '.sh'}
def is_false_positive(line):
ctx = line.lower()
return '...' in ctx or '<example' in ctx or '</example' in ctx
root = Path(os.environ.get('GITHUB_WORKSPACE', '.'))
warnings = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for filename in filenames:
if Path(filename).suffix not in EXTENSIONS:
continue
filepath = Path(dirpath) / filename
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
for lineno, line in enumerate(f.readlines(), 1):
for pattern in PATTERNS:
for match in pattern.finditer(line):
if not is_false_positive(line):
warnings.append(f" {filepath}:{lineno}: {match.group(0)[:40]}...")
except Exception:
pass
if warnings:
print("::error::Potential secret found in committed files:")
for w in warnings:
print(w)
sys.exit(1)
else:
print("::notice::No secrets detected")
PYEOF