Compare commits
11 Commits
fix/lowerc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4df30bdbd6 | |||
| 627d4a7135 | |||
| 75011059e5 | |||
|
|
330da12b5e | ||
| 33a885efab | |||
|
|
bef0813139 | ||
|
|
83c7a879f2 | ||
|
|
69c1ffe0ea | ||
|
|
2309b72814 | ||
|
|
bf6560f4a0 | ||
| 6b299d1d11 |
230
.gitea/workflows/ci.yml
Normal file
230
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,230 @@
|
||||
name: CI
|
||||
|
||||
# Ported from .github/workflows/ci.yml on 2026-05-11 per internal#326
|
||||
# (Class-A root: cross-repo `uses:` blocker for Gitea 1.22.6 —
|
||||
# feedback_gitea_cross_repo_uses_blocked).
|
||||
#
|
||||
# Root cause of the main-red CI on this repo:
|
||||
# The .github/ original used
|
||||
# uses: molecule-ai/molecule-ci/.github/workflows/validate-workspace-template.yml@main
|
||||
# which Gitea 1.22.6 rejects (DEFAULT_ACTIONS_URL=github → 404 against
|
||||
# the remote repo even though it lives on the same Gitea instance).
|
||||
# Gitea reads .github/ as a fallback when .gitea/ is absent
|
||||
# (reference_per_repo_gitea_vs_github_actions_dir), so the .github/
|
||||
# workflow was firing on Gitea and failing in 1s.
|
||||
#
|
||||
# Fix shape: inline the validation logic directly. The canonical
|
||||
# validator in molecule-ai/molecule-ci already self-clones into the
|
||||
# runner via a direct HTTPS `git clone` step (validate-workspace-template.yml
|
||||
# does this verbatim) — so the inline port is just "do that clone +
|
||||
# invoke the validator script in-place", preserving the
|
||||
# single-source-of-truth property (each CI run still fetches the
|
||||
# canonical validator fresh).
|
||||
#
|
||||
# Four-surface migration audit (feedback_gitea_actions_migration_audit_pattern):
|
||||
# 1. YAML — no `workflow_dispatch.inputs`; no `merge_group`; preserved
|
||||
# `on: [push, pull_request]` from the original. Added workflow-level
|
||||
# env.GITHUB_SERVER_URL (feedback_act_runner_github_server_url).
|
||||
# 2. Cache — `actions/setup-python` `cache: pip` preserved; works against
|
||||
# Gitea's built-in cache server when runner.cache is configured.
|
||||
# 3. Token — uses auto-injected GITHUB_TOKEN (Gitea-aliased). Validator
|
||||
# job needs only `contents: read` (no write to issues/PRs).
|
||||
# 4. Docs — anonymous git-clone of molecule-ci (no token in URL); the
|
||||
# molecule-ci repo is public on the Gitea instance.
|
||||
#
|
||||
# Fork-PR semantics: validate-runtime is intentionally skipped on fork
|
||||
# PRs because pip-install + docker-build + adapter-import are arbitrary
|
||||
# code execution. Internal PRs and main pushes get full coverage. The
|
||||
# `github.event.pull_request.head.repo.fork` field is null for non-PR
|
||||
# events; the `!= true` comparison defaults to running.
|
||||
#
|
||||
# Cross-links:
|
||||
# - internal#326 — parent tracking issue
|
||||
# - molecule-ai/molecule-ci/.github/workflows/validate-workspace-template.yml — pattern source
|
||||
# - molecule-ai/molecule-core/.gitea/workflows/ci.yml — Gitea port style reference
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
# Belt-and-suspenders against the runner-default trap
|
||||
# (feedback_act_runner_github_server_url). Runners are configured
|
||||
# with this env via /opt/molecule/runners/config.yaml runner.envs,
|
||||
# but pinning at the workflow level protects against a runner
|
||||
# regenerated without the config file.
|
||||
GITHUB_SERVER_URL: https://git.moleculesai.app
|
||||
|
||||
# Defense-in-depth on the GITHUB_TOKEN scope. The validate-runtime job
|
||||
# runs untrusted-by-design code from the calling repo — pip-installs
|
||||
# requirements.txt (post-install hooks), imports adapter.py, and
|
||||
# docker-builds the Dockerfile. Each primitive can execute arbitrary
|
||||
# code with the token in env. Pinning `contents: read` means the worst
|
||||
# a malicious template PR can do with the token is read public repo
|
||||
# state — no write to issues, no push to branches, no comment-spam.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-static:
|
||||
name: Template validation (static)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
# Canonical validator script lives in molecule-ci, fetched fresh on
|
||||
# every run. Anonymous fetch of the public molecule-ci repo — no
|
||||
# token needed; no actions/checkout cross-repo idiosyncrasies.
|
||||
- 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"
|
||||
# Secret scan — the most important check. Always runs, including
|
||||
# on fork PRs (no third-party code executes here).
|
||||
- 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
|
||||
# Static-only validator — file existence checks, YAML parse,
|
||||
# AST inspection of adapter.py (no import). Doesn't execute any
|
||||
# third-party code; safe on fork PRs.
|
||||
- run: pip install pyyaml -q
|
||||
- run: python3 .molecule-ci-canonical/scripts/validate-workspace-template.py --static-only
|
||||
|
||||
validate-runtime:
|
||||
name: Template validation (runtime)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: validate-static
|
||||
# Skip when the PR comes from a fork — those are external,
|
||||
# untrusted, and would let attackers run pip install / docker build
|
||||
# / adapter.py import on our runner.
|
||||
if: github.event.pull_request.head.repo.fork != true
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- 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: requirements.txt
|
||||
- run: pip install pyyaml -q
|
||||
# Install the template's runtime dependencies so the validator's
|
||||
# check_adapter_runtime_load() can import adapter.py the same way
|
||||
# the workspace container does at boot. Without this, a
|
||||
# syntactically-valid adapter that ImportErrors on a missing
|
||||
# transitive dep would build clean and crash on first user prompt.
|
||||
- if: hashFiles('requirements.txt') != ''
|
||||
run: pip install -q -r requirements.txt
|
||||
- if: hashFiles('requirements.txt') == ''
|
||||
run: pip install -q molecule-ai-workspace-runtime
|
||||
- run: python3 .molecule-ci-canonical/scripts/validate-workspace-template.py
|
||||
- name: Docker build smoke test
|
||||
if: hashFiles('Dockerfile') != ''
|
||||
run: |
|
||||
# Graceful skip when the runner's job-container can't reach the
|
||||
# Docker daemon (e.g. /var/run/docker.sock not mounted into the
|
||||
# act job container, or the in-container uid not in the docker
|
||||
# group). Without this guard, CI stays red even when the
|
||||
# template's Dockerfile is fine — see internal#222 for the
|
||||
# proper runner-config fix.
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "::warning::docker daemon unreachable from runner job container — skipping Docker build smoke (runner-config gap, not a template issue)."
|
||||
exit 0
|
||||
fi
|
||||
docker build -t template-test . --no-cache 2>&1 | tail -5 && echo "Docker build succeeded"
|
||||
|
||||
# Aggregator that emits a single `validate` check name — matches the
|
||||
# historical required-check name on this repo's branch protection.
|
||||
validate:
|
||||
name: validate
|
||||
runs-on: ubuntu-latest
|
||||
needs: [validate-static, validate-runtime]
|
||||
if: always()
|
||||
timeout-minutes: 1
|
||||
steps:
|
||||
- name: Aggregate
|
||||
run: |
|
||||
static="${{ needs.validate-static.result }}"
|
||||
runtime="${{ needs.validate-runtime.result }}"
|
||||
echo "validate-static: $static"
|
||||
echo "validate-runtime: $runtime"
|
||||
if [ "$static" != "success" ]; then
|
||||
echo "::error::validate-static did not succeed: $static"
|
||||
exit 1
|
||||
fi
|
||||
# Treat `skipped` as a pass for fork-PR semantics (validate-runtime
|
||||
# is intentionally skipped on forks; static coverage is the gate).
|
||||
if [ "$runtime" != "success" ] && [ "$runtime" != "skipped" ]; then
|
||||
echo "::error::validate-runtime did not succeed: $runtime"
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Template validation aggregate passed (static=$static, runtime=$runtime)"
|
||||
|
||||
shell-tests:
|
||||
# Shell-level unit tests for scripts/*.sh. Pre-existing
|
||||
# test-derive-provider.sh + test-install-prefix-strip.sh weren't
|
||||
# wired to CI before; landing the new test-load-workspace-config.sh
|
||||
# fix opportunistically closes that gap. Runs in <5s on a fresh
|
||||
# runner. Preserved verbatim from .github/ci.yml.
|
||||
name: Shell unit tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
# PyYAML is required by load-workspace-config.sh's python helper.
|
||||
# In production it's transitive via molecule-ai-workspace-runtime;
|
||||
# in this minimal CI env we install it explicitly so the YAML path
|
||||
# is exercised instead of the script's silent ImportError fallback.
|
||||
- run: pip install -q pyyaml
|
||||
- run: bash scripts/test-derive-provider.sh
|
||||
- run: bash scripts/test-install-prefix-strip.sh
|
||||
- run: bash scripts/test-load-workspace-config.sh
|
||||
28
.github/workflows/publish-image.yml
vendored
28
.github/workflows/publish-image.yml
vendored
@ -26,7 +26,33 @@ permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
# The `.runtime-version` file is the push-mode cascade signal post-
|
||||
# 2026-05-06: when molecule-core/publish-runtime.yml ships a new
|
||||
# version to PyPI, it does NOT call repository_dispatch (Gitea 1.22.6
|
||||
# has no such endpoint — empirically verified molecule-core#20).
|
||||
# Instead it git-pushes an updated `.runtime-version` to each template,
|
||||
# which trips this workflow's `on: push: branches: [main]` trigger.
|
||||
# This job reads that file and forwards the version to the reusable
|
||||
# build workflow.
|
||||
resolve-version:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
outputs:
|
||||
version: ${{ steps.read.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: read
|
||||
run: |
|
||||
if [ -f .runtime-version ]; then
|
||||
v="$(head -n1 .runtime-version | tr -d '[:space:]')"
|
||||
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||
echo "resolved runtime version: $v"
|
||||
else
|
||||
echo "no .runtime-version file present — falling through to Dockerfile default"
|
||||
fi
|
||||
|
||||
publish:
|
||||
needs: resolve-version
|
||||
uses: molecule-ai/molecule-ci/.github/workflows/publish-template-image.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
@ -34,4 +60,4 @@ jobs:
|
||||
# version PyPI just published. Forwarded as a docker --build-arg
|
||||
# so the cache key changes per-version and pip install resolves
|
||||
# freshly. Empty on push/PR — falls back to requirements.txt pin.
|
||||
runtime_version: ${{ github.event.client_payload.runtime_version || inputs.runtime_version || '' }}
|
||||
runtime_version: ${{ github.event.client_payload.runtime_version || inputs.runtime_version || needs.resolve-version.outputs.version || '' }}
|
||||
|
||||
203
.github/workflows/secret-scan.yml
vendored
203
.github/workflows/secret-scan.yml
vendored
@ -1,22 +1,201 @@
|
||||
name: Secret scan
|
||||
|
||||
# Calls the canonical reusable workflow in molecule-core. Defense
|
||||
# against the #2090-class leak (a hosted-agent commit slipping a
|
||||
# credential-shaped string into a PR). Pattern set lives in
|
||||
# molecule-core so we do not maintain a parallel copy here.
|
||||
# Hard CI gate. Refuses any PR / push whose diff additions contain a
|
||||
# recognisable credential. Defense-in-depth for the #2090-class incident
|
||||
# (2026-04-24): GitHub's hosted Copilot Coding Agent leaked a ghs_*
|
||||
# installation token into tenant-proxy/package.json via `npm init`
|
||||
# slurping the URL from a token-embedded origin remote. We can't fix
|
||||
# upstream's clone hygiene, so we gate here.
|
||||
#
|
||||
# Pinned to @staging because that is the active default branch on the
|
||||
# upstream repo (main lags behind via the staging-promotion workflow).
|
||||
# Updates ride along automatically as the upstream regex set evolves.
|
||||
# Inlined copy from molecule-ai/molecule-core/.github/workflows/secret-scan.yml.
|
||||
# Cross-repo workflow_call to a private repo doesn't fully work on Gitea 1.22.6
|
||||
# (workflow file fails parse-time at 0s with no logs); inline keeps the gate
|
||||
# functional until Gitea is upgraded or the canonical scanner moves to a public
|
||||
# repo. When that lands, this file reverts to the 3-line wrapper:
|
||||
#
|
||||
# jobs:
|
||||
# secret-scan:
|
||||
# uses: Molecule-AI/molecule-core/.github/workflows/secret-scan.yml@staging
|
||||
#
|
||||
# Pin to @staging not @main — staging is the active default branch,
|
||||
# main lags via the staging-promotion workflow. Updates ride along
|
||||
# automatically on the next consumer workflow run.
|
||||
#
|
||||
# Same regex set as the runtime's bundled pre-commit hook
|
||||
# (molecule-ai-workspace-runtime: molecule_runtime/scripts/pre-commit-checks.sh).
|
||||
# Keep the two sides aligned when adding patterns.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches: [main, staging, master]
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
branches: [main, staging]
|
||||
|
||||
jobs:
|
||||
secret-scan:
|
||||
uses: molecule-ai/molecule-core/.github/workflows/secret-scan.yml@staging
|
||||
scan:
|
||||
name: Scan diff for credential-shaped strings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 2 # need previous commit to diff against on push events
|
||||
|
||||
# For pull_request events the diff base may be many commits behind
|
||||
# HEAD and absent from the shallow clone. Fetch it explicitly.
|
||||
- name: Fetch PR base SHA (pull_request events only)
|
||||
if: github.event_name == 'pull_request'
|
||||
run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
# For merge_group events the queue's pre-merge ref is a commit on
|
||||
# `gh-readonly-queue/...` whose parent is the queue's base_sha.
|
||||
# That parent isn't part of the queue branch's shallow clone, so
|
||||
# we fetch it explicitly. Without this the diff falls through to
|
||||
# "no BASE → scan entire tree" mode and false-positives on legit
|
||||
# test fixtures (e.g. canvas/src/lib/validation/__tests__/secret-formats.test.ts).
|
||||
|
||||
- name: Refuse if credential-shaped strings appear in diff additions
|
||||
env:
|
||||
# Plumb event-specific SHAs through env so the script doesn't
|
||||
# need conditional `${{ ... }}` interpolation per event type.
|
||||
# github.event.before/after only exist on push events;
|
||||
# merge_group has its own base_sha/head_sha; pull_request has
|
||||
# pull_request.base.sha / pull_request.head.sha.
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
PUSH_BEFORE: ${{ github.event.before }}
|
||||
PUSH_AFTER: ${{ github.event.after }}
|
||||
run: |
|
||||
# Pattern set covers GitHub family (the actual #2090 vector),
|
||||
# Anthropic / OpenAI / Slack / AWS. Anchored on prefixes with low
|
||||
# false-positive rates against agent-generated content. Mirror of
|
||||
# molecule-ai-workspace-runtime/molecule_runtime/scripts/pre-commit-checks.sh
|
||||
# — keep aligned.
|
||||
SECRET_PATTERNS=(
|
||||
'ghp_[A-Za-z0-9]{36,}' # GitHub PAT (classic)
|
||||
'ghs_[A-Za-z0-9]{36,}' # GitHub App installation token
|
||||
'gho_[A-Za-z0-9]{36,}' # GitHub OAuth user-to-server
|
||||
'ghu_[A-Za-z0-9]{36,}' # GitHub OAuth user
|
||||
'ghr_[A-Za-z0-9]{36,}' # GitHub OAuth refresh
|
||||
'github_pat_[A-Za-z0-9_]{82,}' # GitHub fine-grained PAT
|
||||
'sk-ant-[A-Za-z0-9_-]{40,}' # Anthropic API key
|
||||
'sk-proj-[A-Za-z0-9_-]{40,}' # OpenAI project key
|
||||
'sk-svcacct-[A-Za-z0-9_-]{40,}' # OpenAI service-account key
|
||||
'sk-cp-[A-Za-z0-9_-]{60,}' # MiniMax API key (F1088 vector — caught only after the fact)
|
||||
'xox[baprs]-[A-Za-z0-9-]{20,}' # Slack tokens
|
||||
'AKIA[0-9A-Z]{16}' # AWS access key ID
|
||||
'ASIA[0-9A-Z]{16}' # AWS STS temp access key ID
|
||||
)
|
||||
|
||||
# Determine the diff base. Each event type stores its SHAs in
|
||||
# a different place — see the env block above.
|
||||
case "${{ github.event_name }}" in
|
||||
pull_request)
|
||||
BASE="$PR_BASE_SHA"
|
||||
HEAD="$PR_HEAD_SHA"
|
||||
;;
|
||||
*)
|
||||
BASE="$PUSH_BEFORE"
|
||||
HEAD="$PUSH_AFTER"
|
||||
;;
|
||||
esac
|
||||
|
||||
# On push events with shallow clones, BASE may be present in
|
||||
# the event payload but absent from the local object DB
|
||||
# (fetch-depth=2 doesn't always reach the previous commit
|
||||
# across true merges). Try fetching it on demand. If the
|
||||
# fetch fails — e.g. the SHA was force-overwritten — we fall
|
||||
# through to the empty-BASE branch below, which scans the
|
||||
# entire tree as if every file were new. Correct, just slow.
|
||||
if [ -n "$BASE" ] && ! echo "$BASE" | grep -qE '^0+$'; then
|
||||
if ! git cat-file -e "$BASE" 2>/dev/null; then
|
||||
git fetch --depth=1 origin "$BASE" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Files added or modified in this change.
|
||||
if [ -z "$BASE" ] || echo "$BASE" | grep -qE '^0+$' || ! git cat-file -e "$BASE" 2>/dev/null; then
|
||||
# New branch / no previous SHA / BASE unreachable — check the
|
||||
# entire tree as added content. Slower, but correct on first
|
||||
# push.
|
||||
CHANGED=$(git ls-tree -r --name-only HEAD)
|
||||
DIFF_RANGE=""
|
||||
else
|
||||
CHANGED=$(git diff --name-only --diff-filter=AM "$BASE" "$HEAD")
|
||||
DIFF_RANGE="$BASE $HEAD"
|
||||
fi
|
||||
|
||||
if [ -z "$CHANGED" ]; then
|
||||
echo "No changed files to inspect."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Self-exclude: this workflow file legitimately contains the
|
||||
# pattern strings as regex literals. Without an exclude it would
|
||||
# block its own merge.
|
||||
SELF=".github/workflows/secret-scan.yml"
|
||||
|
||||
OFFENDING=""
|
||||
# `while IFS= read -r` (not `for f in $CHANGED`) so filenames
|
||||
# containing whitespace don't word-split silently — a path
|
||||
# with a space would otherwise produce two iterations on
|
||||
# tokens that aren't real filenames, breaking the
|
||||
# self-exclude + diff lookup.
|
||||
while IFS= read -r f; do
|
||||
[ -z "$f" ] && continue
|
||||
[ "$f" = "$SELF" ] && continue
|
||||
if [ -n "$DIFF_RANGE" ]; then
|
||||
ADDED=$(git diff --no-color --unified=0 "$BASE" "$HEAD" -- "$f" 2>/dev/null | grep -E '^\+[^+]' || true)
|
||||
else
|
||||
# No diff range (new branch first push) — scan the full file
|
||||
# contents as if every line were new.
|
||||
ADDED=$(cat "$f" 2>/dev/null || true)
|
||||
fi
|
||||
[ -z "$ADDED" ] && continue
|
||||
for pattern in "${SECRET_PATTERNS[@]}"; do
|
||||
if echo "$ADDED" | grep -qE "$pattern"; then
|
||||
OFFENDING="${OFFENDING}${f} (matched: ${pattern})\n"
|
||||
break
|
||||
fi
|
||||
done
|
||||
done <<< "$CHANGED"
|
||||
|
||||
if [ -n "$OFFENDING" ]; then
|
||||
echo "::error::Credential-shaped strings detected in diff additions:"
|
||||
# `printf '%b' "$OFFENDING"` interprets backslash escapes
|
||||
# (the literal `\n` we appended above becomes a newline)
|
||||
# WITHOUT treating OFFENDING as a format string. Plain
|
||||
# `printf "$OFFENDING"` is a format-string sink: a filename
|
||||
# containing `%` would be interpreted as a conversion
|
||||
# specifier, corrupting the error message (or printing
|
||||
# `%(missing)` artifacts).
|
||||
printf '%b' "$OFFENDING"
|
||||
echo ""
|
||||
echo "The actual matched values are NOT echoed here, deliberately —"
|
||||
echo "round-tripping a leaked credential into CI logs widens the blast"
|
||||
echo "radius (logs are searchable + retained)."
|
||||
echo ""
|
||||
echo "Recovery:"
|
||||
echo " 1. Remove the secret from the file. Replace with an env var"
|
||||
echo " reference (e.g. \${{ secrets.GITHUB_TOKEN }} in workflows,"
|
||||
echo " process.env.X in code)."
|
||||
echo " 2. If the credential was already pushed (this PR's commit"
|
||||
echo " history reaches a public ref), treat it as compromised —"
|
||||
echo " ROTATE it immediately, do not just remove it. The token"
|
||||
echo " remains valid in git history forever and may be in any"
|
||||
echo " log/cache that consumed this branch."
|
||||
echo " 3. Force-push the cleaned commit (or stack a revert) and"
|
||||
echo " re-run CI."
|
||||
echo ""
|
||||
echo "If the match is a false positive (test fixture, docs example,"
|
||||
echo "or this workflow's own regex literals): use a clearly-fake"
|
||||
echo "placeholder like ghs_EXAMPLE_DO_NOT_USE that doesn't satisfy"
|
||||
echo "the length suffix, OR add the file path to the SELF exclude"
|
||||
echo "list in this workflow with a short reason."
|
||||
echo ""
|
||||
echo "Mirror of the regex set lives in the runtime's bundled"
|
||||
echo "pre-commit hook (molecule-ai-workspace-runtime:"
|
||||
echo "molecule_runtime/scripts/pre-commit-checks.sh) — keep aligned."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ No credential-shaped strings in this change."
|
||||
|
||||
1
.runtime-version
Normal file
1
.runtime-version
Normal file
@ -0,0 +1 @@
|
||||
0.1.129
|
||||
@ -71,13 +71,16 @@ ENV PATH="/home/agent/.local/bin:${PATH}"
|
||||
# Until upstream PR #18775 merges, the fork is the only place the patch
|
||||
# exists. Once merged + released, the fork install can be dropped and the
|
||||
# plugin will load against the official wheel unchanged.
|
||||
#
|
||||
# moved to git.moleculesai.app/molecule-ai/hermes-agent (post-suspension migration; see internal#72)
|
||||
# Previously: github.com/HongmingWang-Rabbit/hermes-agent (account suspended 2026-05-06).
|
||||
ARG HERMES_FORK_REF=feat/platform-adapter-plugins
|
||||
ARG HERMES_PLATFORM_MOLECULE_A2A_REF=main
|
||||
# The hermes installer uses uv to create the venv and doesn't seed pip
|
||||
# into it. Bootstrap pip first via ensurepip, then install both wheels.
|
||||
RUN /home/agent/.hermes/hermes-agent/venv/bin/python3 -m ensurepip --upgrade && \
|
||||
/home/agent/.hermes/hermes-agent/venv/bin/python3 -m pip install --no-cache-dir --force-reinstall \
|
||||
"git+https://github.com/HongmingWang-Rabbit/hermes-agent.git@${HERMES_FORK_REF}#egg=hermes-agent" && \
|
||||
"git+https://git.moleculesai.app/molecule-ai/hermes-agent.git@${HERMES_FORK_REF}#egg=hermes-agent" && \
|
||||
/home/agent/.hermes/hermes-agent/venv/bin/python3 -m pip install --no-cache-dir \
|
||||
"git+https://git.moleculesai.app/molecule-ai/hermes-platform-molecule-a2a.git@${HERMES_PLATFORM_MOLECULE_A2A_REF}#egg=hermes-platform-molecule-a2a"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user