Skip to content

fix(security): adversarial review findings — hydrator RCE + name collision + CI dispatch idempotency - #43

Merged
chitcommit merged 1 commit into
mainfrom
fix/adversarial-review-findings
May 26, 2026
Merged

chitcommit merged 1 commit into
mainfrom
fix/adversarial-review-findings

Conversation

@chitcommit

Copy link
Copy Markdown
Contributor

fix(security): adversarial review findings — hydrator RCE, name collision, dispatch idempotency

Adversarial review (via chittycanon-code-cardinal subagent) of the chittymarket
refactor found three high-priority issues. This PR addresses them.

Finding #5 (CRITICAL): hydrate-pointers.sh heredoc RCE sink

Previous: fetched body interpolated into a triple-quoted Python literal via
shell heredoc. Body containing """ or arbitrary Python could escape the
literal and execute in CI with contents: write + pull-requests: write
permissions.

Fix: fetched content goes to a tempfile, then handed to Python via
environment variable (FETCHED_FILE) — NOT shell interpolation. The Python
block reads the file with .read_text(). No injection path.

Finding #2 (HIGH): pointer URL is mutable and unpinned

Previous: prompt_url could point at any URL; no allowlist; no content
hash; Monday cron auto-PRs whatever upstream returned.

Fix:

  • Allowlist of source hosts (currently: raw.githubusercontent.com only)
  • prompt_sha (SHA-256 hex) REQUIRED on every pointer; hydrator verifies
    actual fetched-content hash matches the pin before writing
  • --max-filesize 1048576 cap on curl to bound DoS surface
  • Added prompt_sha: c95066f87361ef9d4909b6bc0fad58f49c2acb424d7927a779bb3b9779db289c
    to plugins/chittyos-core/agents/chittyagent-schema.md (current
    chittyfoundation/chittyschema content hash)

New exit codes: 3=disallowed-host, 4=missing-prompt-sha.

Finding #3 (HIGH): CI didn't re-run dispatch.sh sync

Previous: pre-commit drift hook ran dispatch.sh, but was local-only and
trivially bypassed with --no-verify. The CI workflow ran lint + test

  • manifest-idempotency but NEVER ran dispatch.sh sync to check for
    projection/canonical divergence.

Fix: new "Dispatch idempotency" step in validate-chittymarket.yml.
Re-runs dispatch.sh sync; fails the PR if any canonical/ or plugins/
file changed. Closes the --no-verify bypass at PR time.

Finding #1 (HIGH-LATENT): cross-kind name collision

Previous: dispatch.sh's canonical lookup is first-match-wins by basename
across kind-subdirs (agents → skills → commands → mcp → hooks). Two
canonicals sharing a stem (canonical/agents/foo.md + canonical/skills/foo.md)
would silently project only the first; the second becomes a no-op no-error
orphan.

Fix: new lint-plugins.sh section 8b cross-checks canonical basenames
across all kind-subdirs and errors on any collision.

NOT in this PR (deferred to follow-up)

Findings still open from the adversarial review:

Local: lint clear, 64/64 tests, manifest idempotent, dispatch idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

…sion, dispatch idempotency

Adversarial review (via chittycanon-code-cardinal subagent) of the chittymarket
refactor found three high-priority issues. This PR addresses them.

## Finding #5 (CRITICAL): hydrate-pointers.sh heredoc RCE sink

Previous: fetched body interpolated into a triple-quoted Python literal via
shell heredoc. Body containing `"""` or arbitrary Python could escape the
literal and execute in CI with `contents: write` + `pull-requests: write`
permissions.

Fix: fetched content goes to a tempfile, then handed to Python via
environment variable (`FETCHED_FILE`) — NOT shell interpolation. The Python
block reads the file with .read_text(). No injection path.

## Finding #2 (HIGH): pointer URL is mutable and unpinned

Previous: `prompt_url` could point at any URL; no allowlist; no content
hash; Monday cron auto-PRs whatever upstream returned.

Fix:
  - Allowlist of source hosts (currently: raw.githubusercontent.com only)
  - prompt_sha (SHA-256 hex) REQUIRED on every pointer; hydrator verifies
    actual fetched-content hash matches the pin before writing
  - --max-filesize 1048576 cap on curl to bound DoS surface
  - Added prompt_sha: c95066f87361ef9d4909b6bc0fad58f49c2acb424d7927a779bb3b9779db289c
    to plugins/chittyos-core/agents/chittyagent-schema.md (current
    chittyfoundation/chittyschema content hash)

New exit codes: 3=disallowed-host, 4=missing-prompt-sha.

## Finding #3 (HIGH): CI didn't re-run dispatch.sh sync

Previous: pre-commit drift hook ran dispatch.sh, but was local-only and
trivially bypassed with `--no-verify`. The CI workflow ran lint + test
+ manifest-idempotency but NEVER ran `dispatch.sh sync` to check for
projection/canonical divergence.

Fix: new "Dispatch idempotency" step in validate-chittymarket.yml.
Re-runs dispatch.sh sync; fails the PR if any canonical/ or plugins/
file changed. Closes the --no-verify bypass at PR time.

## Finding #1 (HIGH-LATENT): cross-kind name collision

Previous: dispatch.sh's canonical lookup is first-match-wins by basename
across kind-subdirs (agents → skills → commands → mcp → hooks). Two
canonicals sharing a stem (canonical/agents/foo.md + canonical/skills/foo.md)
would silently project only the first; the second becomes a no-op no-error
orphan.

Fix: new lint-plugins.sh section 8b cross-checks canonical basenames
across all kind-subdirs and errors on any collision.

## NOT in this PR (deferred to follow-up)

Findings still open from the adversarial review:
- #4 No actual codex/openclaw runtime tests — needs runtime test harnesses
- #6 Race conditions in dispatch.sh (no flock), no orphan cleanup, no
  enforcement for declared-but-unimplemented runtimes
- #7 evidence-gate hole — only checked when capabilities.generated.json
  is in the staged diff
- #8 YAML flow-style runtimes silently produce empty list

Local: lint clear, 64/64 tests, manifest idempotent, dispatch idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@chitcommit, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 5 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 21dd34d0-b135-4c75-b06d-0f0fb317540f

📥 Commits

Reviewing files that changed from the base of the PR and between ab9a63c and adda52b.

📒 Files selected for processing (4)
  • .github/workflows/validate-chittymarket.yml
  • plugins/chittyagent-dispatch/scripts/hydrate-pointers.sh
  • plugins/chittyos-core/agents/chittyagent-schema.md
  • scripts/lint-plugins.sh
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/adversarial-review-findings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chitcommit
chitcommit merged commit 7cd6ea7 into main May 26, 2026
4 checks passed
@chitcommit
chitcommit deleted the fix/adversarial-review-findings branch May 26, 2026 23:56

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: adda52b121

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/lint-plugins.sh
Comment on lines +276 to +278
echo "$collisions" | while IFS= read -r line; do
red " ERROR: canonical name collision: $line"
ERRORS=$((ERRORS + 1))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Increment collision error count in parent shell

The new cross-kind collision block prints errors but does not actually fail linting, because echo "$collisions" | while ... runs the loop in a subshell in Bash, so ERRORS=$((ERRORS + 1)) is lost when the loop exits. As a result, commits with canonical basename collisions can still pass scripts/lint-plugins.sh and CI despite the new guard, defeating the intended protection against dispatch shadowing.

Useful? React with 👍 / 👎.

Comment on lines +160 to +161
[ "$NO_SHA" -gt 0 ] && exit 4
[ "$DISALLOWED" -gt 0 ] && exit 3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return a handled exit code for new hard-fail conditions

The script now exits with 3/4 for disallowed hosts and missing prompt_sha, but the hydrate workflow only treats exit code 2 as failure and otherwise continues after set +e (.github/workflows/hydrate-pointers.yml, lines 39-43). That means these new security failures are silently treated as success in CI (no failure and no PR), so pointer integrity violations can go unnoticed.

Useful? React with 👍 / 👎.

m = re.match(r"^---\n(.*?)\n---\n(.*)$", text, re.DOTALL)
if not m:
# Verify SHA-256.
actual_sha=$(shasum -a 256 "$tmp" | awk '{print $1}')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add sha256sum fallback for hash verification

Hash verification is hard-coded to shasum -a 256, which is not guaranteed on all Linux environments (especially minimal containers where sha256sum is present but shasum is not). Because the script runs with set -euo pipefail, missing shasum causes an immediate non-semantic failure before the intended exit-code handling, making hydration fail even for valid pointers.

Useful? React with 👍 / 👎.

run: |
chmod +x plugins/chittyagent-dispatch/scripts/dispatch.sh plugins/chittyagent-dispatch/scripts/adapters/*.sh
bash plugins/chittyagent-dispatch/scripts/dispatch.sh sync > /dev/null
if ! git diff --quiet -- canonical/ plugins/; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Detect untracked files in dispatch idempotency check

The new idempotency gate relies on git diff --quiet -- canonical/ plugins/, which only detects changes to tracked files and ignores newly created untracked files. If dispatch.sh sync materializes missing projection/state files (for example after adding a canonical), this check can still pass even though regeneration produced filesystem drift, so CI can miss exactly the divergence this guard is meant to catch.

Useful? React with 👍 / 👎.

chitcommit added a commit that referenced this pull request May 27, 2026
…/chittyschema#67 (#44)

Upstream PR chittyfoundation/chittyschema#67 (merge 0f3b12e) renamed
chittyconnect-concierge -> chittyagent-connect in the chittyschema-overlord
agent body to align with chittymarket PR #29 family-prefix slug rename.

New content sha256: e344f1df5383cd4b20621edc8b58c9325bb4c6895e19d74363c1a09cb2a5adb1

The hardened hydrator (PR #43) verified the upstream fetch matches this
pin and refreshed the local cache.

Local: lint clear, hydrator idempotent on re-run.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant