Add source-agnostic template fetching for private template families#20
Add source-agnostic template fetching for private template families#20next-devin wants to merge 2 commits into
Conversation
campaign-init fetched starter-template source from a single hardcoded public
repo, so it could not scaffold template families hosted elsewhere (e.g. an
outside, access-controlled repo). This adds a source-agnostic resolver plus an
authenticated fetch path, while keeping page-kit a generic build tool with no
per-family knowledge.
lib/template-source.js (new)
resolveTemplateSource(overrides) overlays caller-supplied {repo, ref,
source_path, private} on the public starter-templates default. It names no
families and no private repos. As the trust boundary, it validates shape and
throws INVALID_SOURCE on a malformed value:
- repo: strict "owner/repo" (rejects traversal, query strings, odd chars)
- ref: branch/tag/SHA; slashes allowed (feature/foo, refs/heads/x) but no
"..", leading/trailing slash, or odd chars
- source_path: a relative POSIX subtree only (forward slashes, no "..",
no absolute/"~") so extraction can never path.join out of the tree
lib/actions/init.js
- New flags: --source-repo / --source-ref / --source-path / --private.
Default (no flags) = public starter-templates, anonymous — unchanged.
--private fetches an authenticated tarball (GITHUB_TOKEN/GH_TOKEN or
`gh auth token`), skips the public picker, and requires --template.
- Missing token -> exit 5 (MISSING_INPUT, code MISSING_GITHUB_TOKEN); no fetch
attempted. Malformed --source-* -> exit 6 (INVALID_INPUT, INVALID_SOURCE).
- Authenticated fetch follows only https redirects to GitHub-owned hosts; the
token is dropped on the signed redirect and never serialized into output.
- extractTemplate honors source_path (built with path.posix.join, so the
subtree contract is platform-independent) instead of hardcoding src/.
- A --private source's display name is refined from the source repo's
campaigns.json when --name is omitted.
The opinionated part -- which family lives in which private repo -- stays in the
orchestrating layer (campaigns-os / the operator's invocation), never here.
208 tests (resolver, validation, redirect host-pinning, existing init tests).
Verified: --template <slug> --source-repo <owner/repo> --private extracts an
authenticated tarball; --private without a token exits 5; a bad --source-path
exits 6; public --template <slug> with no flags resolves anonymously.
Supersedes #19 (same net change, clean history).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Code Review SummaryStatus: 1 Issue Found | Recommendation: Merge (minor gap) Overview
All 8 issues from the previous review have been resolved by this commit:
Issue Details (click to expand)SUGGESTION
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous Review Summary (commit 329010e)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 329010e)Status: 8 Issues Found | Recommendation: Address before merge Overview
The PR cleanly adds a source-agnostic resolver + authenticated tarball fetch, with a strong trust boundary in Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (3 files)
Reviewed by minimax-m3 · 311,693 tokens |
…t host Kilo review on #20: - template-source.js: both tarball URLs now take a PLAIN ref. The anonymous codeload URL dropped its "refs/heads/" prefix (tar.gz/<ref>), matching the authed api.github.com tarball/<ref> form — so a tag or SHA ref works on both and api.github.com no longer 404s on a refs/heads/ value. (WARNING x2) - template-source.js: ai_context_doc_url is now always derived from the PUBLIC default repo, so it can't 404 for a --private source. (SUGGESTION) - init.js: AI-context URL is resolved lazily via aiContextDocUrl() instead of a module-load constant, so a future validation tightening can't turn an import into a hard module-load failure. (WARNING) - init.js: `gh auth token` runs with a 5s timeout; a wedged gh is treated as "no token" (actionable MISSING_GITHUB_TOKEN) instead of hanging. (WARNING) - init.js: the untrusted-redirect error reports only the host, not the full signed URL. (SUGGESTION) - test: e2e test that fetchBufferAuthed DROPS the Authorization header when it follows the 302 to codeload (mocks https.get) — the security guarantee the helper exists for. (SUGGESTION) 210 tests green. Verified public (anonymous tar.gz/main) and private (authed tarball/main) both still extract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| // End-to-end: the authed fetch must DROP the Authorization header when it follows | ||
| // the signed 302 to codeload — the whole reason fetchBufferAuthed exists. Mock the | ||
| // shared https.get and assert the redirect request carries no token. | ||
| test('fetchBufferAuthed drops the Authorization header on redirect', async () => { |
There was a problem hiding this comment.
SUGGESTION: The new e2e test covers the trusted-redirect / drop-Authorization path, but the untrusted-redirect / reject path of fetchBufferAuthed is still only covered by the isTrustedGithubRedirect unit test. Consider adding a negative case (e.g. a 302 to https://attacker.example/... asserting the promise rejects with the "untrusted host" error) to lock in the integration of the trust check with the authed fetch helper.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
This feature was done in #21 |
|
Closing — superseded by #21 (merged, v0.2.0), which solves this more completely and is the canonical mechanism now. Named sources in |
What
campaign-initfetched starter-template source from one hardcoded public repo, so it couldn't scaffold template families hosted elsewhere (e.g. an outside, access-controlled repo). This adds a source-agnostic resolver + authenticated fetch — while keeping page-kit a generic build tool that holds no per-family knowledge.Design
lib/template-source.js(new) —resolveTemplateSource(overrides)overlays caller-supplied{repo, ref, source_path, private}on the public starter-templates default. Names no families, no private repos. As the trust boundary it validates shape (INVALID_SOURCEon bad repo / ref / traversing or non-POSIXsource_path).lib/actions/init.js— new--source-repo / --source-ref / --source-path / --privateflags.--private: authenticated tarball (GITHUB_TOKEN/GH_TOKENorgh auth token), skips the public picker, requires--template. Token dropped on the signed redirect (pinned to https + GitHub-owned hosts), never serialized.--source-*→ exit 6.extractTemplatehonorssource_pathviapath.posix.join(platform-independent), instead of hardcodingsrc/.The opinionated part — which family lives in which private repo — stays in the orchestrating layer (campaigns-os / the operator's invocation), never in page-kit.
Verification
--template <slug> --source-repo <owner/repo> --private→ authenticated extract; name refined fromcampaigns.json.--privatewithout a token → exit 5; bad--source-path→ exit 6.--template <slug>with no flags → anonymous (unchanged).Supersedes #19 — identical net change, but presented as one clean commit instead of #19's build-then-refactor history. Pairs with campaigns-os#101 (the Arjuna recognition contracts; repo-agnostic).