85 Commits

  • Merge pull request #2241 from itkdm/feat/add-vue-ecosystem
    feat: add Vue ecosystem review support (vue-reviewer agent, /vue-review command, vue-patterns skill). Duplicate rules/vue/* kept from #2250; catalog counts reconciled.
  • fix: prevent IOC scanner false positives on hook filenames and scan .cursor configs (#2245)
    * fix: prevent IOC scanner false positives on hook filenames and scan .cursor configs
    
    The supply-chain IOC scanner matched CRITICAL_TEXT_INDICATORS with plain
    substring search, so legitimate hook filenames that merely end with a known
    payload name (e.g. the stock Cursor hook before-shell-execution.js vs the
    payload execution.js) were flagged as CRITICAL. Indicator matching now
    requires a non-filename character before the match.
    
    Also add .cursor/ to the special config paths so Cursor hooks.json files
    (a known persistence vector already listed in PERSISTENCE_FILENAMES) are
    actually inspected in normal checkouts - previously they were only scanned
    by accident when the repo path happened to contain /.claude/.
    
    * test: cover underscore-prefixed filenames in IOC boundary suppression
    
    Make explicit that '_' is treated as a filename word character, so
    snake_case hook names like post_execution.js are intentionally not
    flagged by the execution.js indicator (real payload references appear
    after '/', quotes, or whitespace).
  • fix: make plugin hooks run on Node 21+ and green the suite under modern Node (#2184)
    ROOT CAUSE: hooks load plugin-hook-bootstrap.js via
    `node -e "...; process.argv.splice(1,0,s); require(s)"`. On Node 21+,
    require.main is `undefined` under --eval, so the `if (require.main === module)`
    guard was false and main() never ran — every plugin hook silently no-op'd
    (e.g. the MCP-health PreToolUse hook stopped blocking). CI (Node 18/20) hid
    this; it only surfaces on Node 21+. Fix: also run main() when require.main is
    undefined (the eval-bootstrap case), while staying dormant on real imports.
    
    Also clears pre-existing main debt the full local suite enforces:
    - catalog:sync — README/docs agent+skill counts drifted after recent merges
    - tests/ci/supply-chain-watch-workflow: update checkout SHA to the merged v6.0.3 (#2183)
    - markdownlint + check-unicode-safety --write across docs/skills
    
    Suite: 2683/2683 green under Node v25; lint + unicode clean.
    
    Co-authored-by: ECC Test <ecc@example.test>
  • fix(skills): keep curl credentials out of argv (#2175)
    * fix(skills): avoid curl credential argv leaks
    
    * test(ci): guard secret curl examples
  • test: guard broken-symlink tests so the suite passes on Windows (#2176)
    * test: guard broken-symlink tests so the suite passes on Windows
    
    Four test cases create a dangling symlink with fs.symlinkSync() to exercise
    statSync catch branches, but did not guard for platforms where symlink
    creation is not permitted. On Windows without Developer Mode / admin rights,
    fs.symlinkSync throws EPERM, so these tests fail and `npm test` is red:
    
      - tests/ci/validators.test.js (Round 73, validate-commands skill entry)
      - tests/lib/session-manager.test.js (Round 83, getAllSessions)
      - tests/lib/session-manager.test.js (Round 84, getSessionById)
      - tests/lib/utils.test.js (Round 84, findFiles)
    
    Wrap each symlinkSync in try/catch and skip cleanly on failure, mirroring the
    existing convention already used in this repo (validators.test.js Round 57 and
    hooks/config-protection.test.js). On Linux/macOS and admin Windows the symlink
    still succeeds and the tests run unchanged; only the unsupported-symlink path
    now skips instead of failing.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * test: only skip symlink tests on EPERM/EACCES, rethrow other errors
    
    Address CodeRabbit review: the catch blocks swallowed every error, which could
    mask a real test/setup failure as a false skip. Inspect err.code and only take
    the skip path for EPERM/EACCES (symlink creation blocked, e.g. Windows without
    Developer Mode); rethrow anything else so genuine failures still surface.
    
    Per the repo coding guideline: never silently swallow errors.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
  • feat: add dynamic workflow team orchestration surface
    Adds dynamic workflow/team orchestration skills, the content pack, and control-pane work-item/Kanban state DB support. Includes reviewer hardening for state-db CLI validation, optional state DB failure handling, and mergeStateStatus projection.
  • test(ci): coverage for round-1 fixes (quoted write-all, dedup, lifecycle scope)
    Three test changes in response to the round-1 review:
    
    1. **Add quoted-write-all coverage** (cubic P0 follow-up).
       Two new cases assert the regex now matches the double-quoted and
       single-quoted YAML forms of `permissions: "write-all"`:
         - `rejects double-quoted permissions: "write-all"`
         - `rejects single-quoted permissions: 'write-all'`
       Both fixtures trigger only the persist-credentials gate, so they
       exercise the WRITE_ALL_PATTERN OR-clause in isolation.
    
    2. **Add expression+ref dedup coverage** (greptile P2 follow-up).
       `emits a single violation when both expressionPattern and refPattern
       match the same step` — uses `refs/pull/${{ … head.sha }}/merge` as
       the fixture (which matches both patterns) and counts ERROR lines for
       the `pull_request_target` rule, asserting exactly one. Re-introducing
       the duplicate-push bug would re-fail this test immediately.
    
    3. **Drop the `npm ci without --ignore-scripts under write-all` test**
       (greptile P2). That test happened to pass under the previous
       `--ignore-scripts` regex, but `UNSAFE_INSTALL_PATTERNS` (added in
       `f7035b56`) fires unconditionally for every workflow regardless of
       permissions. So the test was exercising a pre-existing code path
       that has nothing to do with WRITE_ALL_PATTERN. Reviewer flagged this
       could mislead future contributors into thinking lifecycle-script
       enforcement is gated on write permissions.
    
       Replaced by the surrounding `rejects checkout credential persistence
       in workflows with permissions: write-all` test (already present) and
       the new quoted-form tests above, which all exercise the actual
       persist-credentials gate that the WRITE_ALL_PATTERN clause newly
       activates.
    
    Test count: 22 → 24 (added 3 new, dropped 1). All green; `yarn lint`
    clean.
    
    The cohort comment above the write-all block was also tightened to
    explicitly note that "the lifecycle-script gate already fires
    unconditionally for every workflow" so the next reader sees the
    distinction up front.
  • fix(ci): flag refs/pull checkouts under pull_request_target
    The `pull_request_target` rule's `expressionPattern` matches only
    the canonical `github.event.pull_request.head.{ref,sha,repo.full_name}`
    interpolations. It does not match the second canonical form of
    the same exploit — fetching `refs/pull/<N>/{head,merge}` directly:
    
      - uses: actions/checkout@v4
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/merge
    
    The merge-ref variant is what GitHub's own security guidance calls
    out as the highest-severity privilege-escalation pattern under
    `pull_request_target`: it materialises the PR's merge commit
    (attacker code spliced with base), executes inside a workflow that
    has full repo-scoped tokens, and gives the attacker the chance to
    exfiltrate secrets or push to default branches. `refs/pull/N/head`
    is functionally equivalent — same source, same trust boundary.
    
    Reproduced on `main` before this commit:
    
      $ cat /tmp/bad.yml
      name: bad
      on: { pull_request_target: { types: [opened] } }
      permissions: { contents: read }
      jobs:
        do:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
              with:
                ref: refs/pull/${{ github.event.pull_request.number }}/merge
                persist-credentials: false
            - run: npm ci --ignore-scripts
    
      $ ECC_WORKFLOWS_DIR=/tmp node scripts/ci/validate-workflow-security.js
      Validated workflow security for 1 workflow files
      $ echo $?
      0
    
    Expected: violation flagging the refs/pull checkout under pull_request_target.
    Actual: passes silently.
    
    Fix: add a `refPattern` to the `pull_request_target` rule:
    
        /^\s*ref:\s*['"]?[^'"\n]*refs\/(?:remotes\/)?pull\/[^'"\n\s]+/m
    
    and apply it per checkout step inside the existing
    event-gated loop. The pattern matches the ref VALUE so it catches
    all interpolation shapes — `refs/pull/123/head`,
    `refs/pull/${{ github.event.pull_request.number }}/merge`,
    `${{ env.FOO }}/refs/pull/N/head` — without enumerating the
    possible interpolations themselves.
    
    Scoping: the rule is already gated on the workflow containing
    `pull_request_target:`, so non-privileged `pull_request` workflows
    that legitimately check out a PR ref are not affected.
    
    After this commit the reproduction above exits 1 with:
    
      ERROR: bad.yml:10 - pull_request_target must not checkout an untrusted pull_request head ref/repository
    
    Three new regression tests in `tests/ci/validate-workflow-security.test.js`:
      - rejects pull_request_target + refs/pull/<N>/merge
      - rejects pull_request_target + hardcoded refs/pull/<N>/head
      - allows pull_request_target with no `with.ref:` (base-ref checkout —
        the safe pattern from GitHub's own guidance)
    
    Test count: 17 → 20 in this file; full `yarn test` still green.
    
    Together with the previous commit, this closes the two
    independent `validate-workflow-security.js` bypasses I found.
  • fix(ci): treat 'permissions: write-all' as a write-permission gate
    `WRITE_PERMISSION_PATTERN` in `validate-workflow-security.js`
    enumerates named GitHub Actions scopes (`contents: write`,
    `issues: write`, etc.) to decide whether a workflow needs to:
      - disable `persist-credentials` on `actions/checkout`
      - pass `--ignore-scripts` to `npm ci`
    
    The pattern misses the top-level shorthand `permissions:
    write-all`, which is the strictly broader form — it grants every
    named scope write access in a single line. As a result, a
    workflow that opts into write-all currently slips both gates.
    
    Reproduced on `main` before this commit:
    
      $ cat /tmp/bad.yml
      name: bad
      on: [push]
      permissions: write-all
      jobs:
        do:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
            - run: npm ci
    
      $ ECC_WORKFLOWS_DIR=/tmp node scripts/ci/validate-workflow-security.js
      Validated workflow security for 1 workflow files
      $ echo $?
      0
    
    Expected: at least two violations (missing `persist-credentials:
    false`, missing `--ignore-scripts`).
    Actual: passes silently.
    
    Fix: add a sibling pattern `WRITE_ALL_PATTERN` that matches
    `^\s*permissions:\s*write-all\b` and OR it with
    `WRITE_PERMISSION_PATTERN` at the single gate. Both top-level
    and job-level `permissions:` blocks satisfy the `^\s*` prefix.
    
    After this commit the reproduction above exits 1 with:
    
      ERROR: bad.yml:8 - workflows with write permissions must disable checkout credential persistence
      ERROR: bad.yml:9 - workflows with write permissions must install npm dependencies with --ignore-scripts
    
    Three new regression tests in `tests/ci/validate-workflow-security.test.js`:
      - rejects write-all + credential-persisting checkout
      - rejects write-all + `npm ci` without `--ignore-scripts`
      - allows write-all when both gates are satisfied (no over-block)
    
    Test count: 14 → 17 in this file; full `yarn test` still green.
    
    A separate `refs/pull/N/merge` bypass under `pull_request_target`
    exists in the same validator and is fixed in the next commit.
  • feat: add command registry and coverage checks (#1906)
    Salvages the useful parts of #1897 without generated .caliber state or stale counts.
    
    - adds a deterministic command registry generator and drift check
    - commits the current command registry for 75 commands
    - validates the rc.1 README catalog summary against live counts
    - adds a single Ubuntu Node 20 coverage job instead of running coverage in every matrix cell
    
    Co-authored-by: jodunk <jodunk@users.noreply.github.com>
  • docs: add supply-chain incident response playbook
    Add a repo-level supply-chain incident response playbook for npm/GitHub Actions package-registry incidents, anchored on the May 2026 TanStack compromise and prior Shai-Hulud-style npm incidents.
    
    - add `docs/security/supply-chain-incident-response.md` with exposure checks, immediate response steps, workflow rules, publication rules, and escalation triggers
    - link the playbook from `SECURITY.md`
    - reject `pull_request_target` workflows that restore or save shared dependency caches
    - add a regression test for the new `pull_request_target + actions/cache` guardrail
    
    Validation:
    - node tests/ci/validate-workflow-security.test.js (12 passed, 0 failed)
    - node scripts/ci/validate-workflow-security.js (validated 7 workflow files)
    - npx markdownlint-cli 'SECURITY.md' 'docs/security/supply-chain-incident-response.md'
    - npx markdownlint-cli '**/*.md' --ignore node_modules
    - git diff --check
    - node tests/run-all.js (2377 passed, 0 failed)
    - GitHub CI for #1848 green across Ubuntu, Windows, and macOS
    
    No release, tag, npm publish, plugin tag, marketplace submission, or announcement was performed.
  • ci: require npm audit signature checks
    Require npm registry signature verification wherever workflow npm audit checks run.
    
    - add npm audit signatures to CI Security Scan and maintenance security audit jobs
    - teach the workflow security validator to reject npm audit without signature verification
    - keep the repair and Copilot prompt tests portable across Windows path/case and CRLF frontmatter behavior
    
    Validation:
    - node tests/run-all.js (2376 passed, 0 failed)
    - CI current-head matrix green on #1846
  • ci: harden workflow install boundaries
    - run non-test workflow installs with npm ci --ignore-scripts where lifecycle scripts are not needed\n- reject plain npm ci in workflows with write permissions\n- reject actions/cache in id-token: write workflows to reduce OIDC publish cache-poisoning risk
  • docs: salvage focused stale PR contributions
    - add Vite and Redis pattern skills from closed stale PRs
    
    - add frontend-slides support assets
    
    - port skill-comply runner fixes and LLM prompt/provider regressions
    
    - harden agent frontmatter validation and sync catalog counts
  • fix: harden CI validators
    Ports personal-path validator hardening and quoted checkout detection onto current main.
  • fix: sync skill frontmatter and catalog counts
    Adds missing skill frontmatter, normalizes strict YAML metadata, syncs README catalog counts, and extends catalog validation for README/plugin/marketplace count drift.
  • fix(ci): flag SKILL.md frontmatter defects in validate-skills (#1669)
    * fix(ci): flag SKILL.md frontmatter defects in validate-skills
    
    Issue #1663 reported two SKILL.md frontmatter defects (missing `name:`
    on skill-stocktake; literal block-scalar `description: |-` on
    openclaw-persona-forge) that PR #1664 addresses at the data level.
    
    This change is complementary: it extends `scripts/ci/validate-skills.js`
    to catch the same class of defect statically going forward, so the
    frontmatter-vs-renderer problems do not silently reappear as new skills
    land.
    
    ## Checks added
    - Frontmatter must declare a `name:` field.
    - Frontmatter `description:` must not use a literal block scalar
      (`|` / `|-` / `|+`) — these preserve internal newlines and break
      flat-table renderers keyed off `description`. Folded (`>`) and inline
      strings are accepted.
    
    ## Behavior
    - Frontmatter findings default to WARN (exit 0) so this PR does not
      break CI while the two known offenders are still on main. Pass
      `--strict` or set `CI_STRICT_SKILLS=1` to promote them to ERROR
      (exit 1). Structural findings (missing / empty SKILL.md) remain
      errors as before.
    - Today against main, the validator reports exactly two warnings —
      the same two files called out in #1663 — and exits 0. When #1664
      lands, the validator reports zero warnings, at which point strict
      mode can be enabled in CI.
    
    ## Parser notes
    - Bespoke frontmatter parser mirrors the style of `validate-agents.js`
      (tolerant of UTF-8 BOM and CRLF; no new npm dependency).
    - Block-scalar continuation lines are skipped so keys inside a block
      scalar are not mistaken for top-level keys.
    - Hidden directories (`.something/`) under skills/ are now skipped.
    
    ## Tests
    Adds five focused tests to `tests/ci/validators.test.js`:
    - warns when frontmatter is missing `name` (default mode)
    - errors when frontmatter is missing `name` (--strict mode)
    - warns on literal block-scalar description (|-)
    - accepts folded (>) and inline descriptions under --strict
    - skips hidden directories under skills/
    
    ## Docs
    Adds two bullets to the `Skill Checklist` in CONTRIBUTING.md covering
    the two rules now surfaced by the validator.
    
    Refs #1663. Complements (does not compete with) #1664.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * fix(ci): harden SKILL.md frontmatter checks after bot review
    
    Address findings from CodeRabbit, Greptile, and cubic on #1669:
    
    - Guard empty or whitespace-only `name:` values. Previously
      `name:    ` silently passed because the presence check only
      tested key-set membership; now inspectFrontmatter captures
      trimmed values and validate flags an explicit 'name is empty'
      WARN/ERROR.
    - Broaden block-scalar detection to cover YAML 1.2 indent
      indicators (`|2`, `|-2`, `>2-`) and trailing comments
      (`|-  # note`). The old regex required a bare `|`/`>` with
      optional `+`/`-`, which let valid-but-disallowed forms slip
      through.
    - Update CONTRIBUTING.md checklist to list `|+` alongside `|`
      and `|-` for parity with the validator.
    - Extend runSkillsValidator to accept env overrides and add four
      regression tests: empty name, |+ description, |-2 + comment, and
      CI_STRICT_SKILLS=1.
    
    * fix(ci): address round-2 review on validate-skills frontmatter
    
    - Tighten extractFrontmatter closing delimiter to require a newline or
      end-of-file after the closing `---`, so body lines beginning with
      `---text` are not parsed as frontmatter (CodeRabbit).
    - Strip both trailing and comment-only values in inspectFrontmatter, so
      `name: # todo` is surfaced as empty rather than silently passing
      (cubic P2).
    - Extract validateSkillDir helper so the per-directory validation
      block moves out of validateSkills, keeping both functions under the
      50-line guideline (CodeRabbit nit).
    - Hoist runSkillsValidator to module scope in the test harness and
      share the spawnSync import with execFileSync so the helper stops
      re-requiring child_process on every invocation (CodeRabbit nit).
    - Add regression tests: comment-only `name:` values must fail strict
      mode; `---trailing` body lines must not be parsed as frontmatter.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * Update tests/ci/validators.test.js
    
    Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>