molecule-ci#2 attempted token: '' to force anonymous on the cross-repo checkout. CI on plugin-molecule-careful-bash@663bf72 (post-merge of #2) revealed actions/checkout@v4 errors with: ::error::Input required and not supplied: token Even though token's input definition is required:false with a default, the action's runtime auth-helper calls getInput('token', {required: true}) internally — empty string fails that check. Fix: replace the cross-repo actions/checkout with a direct git clone shell step. molecule-ci is public; anonymous git clone has neither the auth-trips-Gitea-404 problem (#2's target) nor the empty-token-input- required problem (#2's actual failure shape). 3 files updated, 4 sites total: * validate-plugin.yml (1 site) * validate-workspace-template.yml (2 sites) * validate-org-template.yml (1 site) Refs: internal#46. Closes the third root cause uncovered by the verification cycle on plugin-molecule-careful-bash. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87 lines
4.0 KiB
YAML
87 lines
4.0 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.
|
|
# Direct git-clone instead of actions/checkout@v4 because:
|
|
# (a) actions/checkout@v4 sends Authorization: basic <github.token> by default,
|
|
# and Gitea 404s the cross-repo authenticated request (different from
|
|
# GitHub which falls back to anon-public-read).
|
|
# (b) Passing token: '' triggers actions/checkout's runtime "Input required
|
|
# and not supplied: token" error — the input is documented as
|
|
# required:false but the action's runtime calls getInput with
|
|
# required:true on its auth-helper path.
|
|
# Anonymous git clone of public molecule-ci has neither problem.
|
|
# See molecule-ci#1 (lowercase fix) + #2 (token:'' attempt) +
|
|
# the post-merge CI run on plugin-molecule-careful-bash@663bf72.
|
|
- name: Fetch molecule-ci canonical scripts
|
|
run: git clone --depth 1 https://git.moleculesai.app/molecule-ai/molecule-ci.git .molecule-ci-canonical
|
|
- 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
|