fix(harness): fetch full skill directory from URL base via ForgeClient#2707
Conversation
PR Summary by QodoFix URL-base skill composition to fetch full directory via ForgeClient Description
Diagram
High-Level Assessment
Files changed (6)
|
Site previewPreview: https://04087832-site.fullsend-ai.workers.dev Commit: |
|
🤖 Review · |
ae4ce40 to
1c159e0
Compare
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Code Review by Qodo
1. Allowlist path-scope bypass
|
| allowedBy := matchingAllowedPrefix(skillFileURL, allowlist) | ||
| if allowedBy == "" { | ||
| return Dependency{}, "", fmt.Errorf("base %s: URL %q is not in allowed_remote_resources", field, skillFileURL) | ||
| } | ||
|
|
||
| // Check cache first (keyed by SKILL.md URL for backward compat). | ||
| hash, indexHit := urlIndexLookup(opts.WorkspaceRoot, skillFileURL) | ||
| if indexHit { | ||
| treeHash, ok := urlIndexLookup(opts.WorkspaceRoot, "skill:"+skillFileURL) |
There was a problem hiding this comment.
2. Allowlist path-scope bypass 🐞 Bug ⛨ Security
fetchBaseSkill only validates allowed_remote_resources against the SKILL.md URL, but when ForgeClient is set it downloads every file in the skill directory without checking that each companion path is allowed. If an allowlist intentionally whitelists only the SKILL.md URL (or other narrow prefixes), ForgeClient implicitly expands what can be fetched beyond that policy intent.
Agent Prompt
## Issue description
`fetchBaseSkill()` checks `allowed_remote_resources` only for `.../SKILL.md`, but the ForgeClient path fetches additional sibling/nested files without any allowlist validation. This changes the effective policy from “only this URL/prefix is allowed” to “any file in this directory is allowed” when ForgeClient is available.
## Issue Context
- `allowed_remote_resources` is enforced via prefix matching (`MatchingAllowedPrefixInList`).
- The ForgeClient directory path should respect the same allowlist semantics as the URL fetch path.
## Fix Focus Areas
- internal/harness/compose.go[783-905]
## Suggested fix approach
1. Before doing a ForgeClient directory fetch, ensure the allowlist authorizes directory-wide access (e.g., require that `matchingAllowedPrefix(skillDirURL+"/", allowlist)` is non-empty and use that as `allowedBy`).
2. Additionally (or instead), for each `e.Path` fetched, synthesize the corresponding raw URL (`skillDirURL + "/" + e.Path`) and require `matchingAllowedPrefix(...)` to succeed before fetching/caching that file.
3. If a file is not allowed, either:
- fail with a clear error stating which path is blocked, or
- skip the file and attach a warning (but be careful: skipping companion files reintroduces the original bug for multi-file skills).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
🤖 Finished Review · ❌ Failure · Started 9:56 PM UTC · Completed 9:57 PM UTC |
ggallen
left a comment
There was a problem hiding this comment.
The core fix is right — threading ForgeClient into ComposeOpts and using ListDirectoryContents + GetFileContentAtRef to fetch full skill directory trees. But the SKILL.md-only fallback needs to go. It preserves exactly the silent-degradation failure mode that caused the v0.22.0 outage. Skills should either fully resolve via the forge API or fail fast — one code path, no fallback.
Removing the fallback also lets you delete isSkillMDOnly, the conditional warning logic, and most of the old SKILL.md-only fetch path. See inline comments for specifics.
1c159e0 to
e177a2e
Compare
|
🤖 Finished Review · ❌ Failure · Started 2:04 AM UTC · Completed 2:05 AM UTC |
e177a2e to
b4a26d3
Compare
|
🤖 Finished Review · ❌ Failure · Started 2:09 AM UTC · Completed 2:10 AM UTC |
b4a26d3 to
e7d5d55
Compare
|
🤖 Review · |
When a per-org .fullsend repo uses `base:` to inherit skills from a remote repository, v0.22.0 only fetched SKILL.md and silently dropped companion files (meta-prompt.md, sub-agents/, scripts/). This broke the review agent for all per-org installations. Changes: - Add ParseRawContentURL to bridge raw.githubusercontent.com URLs to ForgeURLInfo for GitHub Contents API access - Teach fetchBaseSkill to use ForgeClient.ListDirectoryContents + GetFileContentAtRef for full skill directory fetch with tree caching - Add ForgeClient field to ComposeOpts; wire it in run.go and lock.go via best-effort token resolution (GH_TOKEN / GITHUB_TOKEN / gh auth) - Fail fast when ForgeClient is unavailable instead of silently degrading to SKILL.md-only fetch - Add directory-level allowlist validation in fetchBaseSkillDir to prevent companion file fetches from bypassing allowed_remote_resources - Add FullListing field to DirCacheEntry to distinguish v0.23+ entries from v0.22.0 SKILL.md-only caches, preventing perpetual re-fetch of legitimate single-file skills - Serve stale v0.22.0 cache entries in offline mode rather than erroring with "not in cache" Fixes #2705 Assisted-by: Claude (fix), Gemini (review), Codex (review) Signed-off-by: Wayne Sun <gsun@redhat.com>
e7d5d55 to
dca90b1
Compare
|
🤖 Finished Review · ✅ Success · Started 2:04 PM UTC · Completed 2:17 PM UTC |
ralphbean
left a comment
There was a problem hiding this comment.
LGTM. The latest commit addresses all of the prior review feedback — fallback removed, allowlist check added, lock.go wiring fixed, validation added, dead code cleaned up.
ReviewFindingsMedium
Labels: PR fixes a bug in harness skill directory fetching with Go code changes. |
All review threads resolved; changes addressed in dca90b1
|
🤖 Finished Retro · ✅ Success · Started 2:50 PM UTC · Completed 3:00 PM UTC |
Retro: PR #2707 — Fix URL-base skill companion file resolutionTimeline: Issue #2705 reported that v0.22.0 URL-base skill resolution only fetched SKILL.md, dropping companion files (sub-agents, scripts). The code agent produced PR #2707 at ~21:03 UTC June 26 (~15min run, successful). However, the review agent then hit a systemic infrastructure failure window lasting ~12 hours (23:19 UTC June 26 through ~12:13 UTC June 27) where agents exited in 10-20 seconds without producing output — affecting PR #2707 and at least two other PRs (#2701, #2537). Human reviewer ggallen provided thorough review at 00:07 UTC June 27 finding 5 significant issues (silent fallback preserving the outage failure mode, missing nil check in lock.go, audit log corruption, missing URL validation, wrong error default). By the time the review agent succeeded at 14:01 UTC June 27, all human feedback had been addressed. The agent found 1 valid medium finding (forge-abstraction-violation: Review quality: The review agent's single finding was valid and additive — it caught a repo convention violation (AGENTS.md forge abstraction rule) that the human reviewers didn't mention. However, the agent never reviewed the original problematic code due to infrastructure failures. The 5 issues ggallen found (fail-open fallback, nil check, audit corruption, input validation, error default) all fall within the agent's sub-agent guidance — particularly the correctness and security sub-agents' explicit coverage of fail-open patterns, nil handling, and error paths. We cannot assess whether the agent would have caught them because it never ran against the original code. Skipped proposals (covered by existing issues):
Proposals filed
|
Summary
ParseRawContentURL()to parseraw.githubusercontent.comURLs into forge info (owner/repo/ref/path)ForgeClientfield toComposeOptssofetchBaseSkill()can useListDirectoryContents+GetFileContentAtRefto fetch the full skill directory treefullsend lockfor offline pre-cachingParseRawContentURLlock.goto match therun.gopatternForgeClientintoComposeOptsin bothrun.goandlock.goCLI callersImpact
Only the
pr-reviewskill has companion files today (meta-prompt.md,sub-agents/). Other skills (code-review,code-implementation,fix-review,issue-labels,retro-analysis) are SKILL.md-only and are unaffected by this bug or this fix.Test plan
ParseRawContentURL()— 8 unit tests covering valid URLs, edge cases, wrong host, missing segmentsfetchBaseSkill()withFakeClient— full-directory fetch, API error, missing SKILL.md, no ForgeClient error, parse error, audit errorgo build ./...— compiles cleanlygo test ./internal/forge/... ./internal/harness/... ./internal/cli/...— all pass