Skip to content

detect GitHub repos in Brain capture and offer post-clone scan/study agents - #3628

Merged
atomantic merged 2 commits into
mainfrom
cos/task-mskjoopy/agent-50c3d4af
Aug 8, 2026
Merged

detect GitHub repos in Brain capture and offer post-clone scan/study agents#3628
atomantic merged 2 commits into
mainfrom
cos/task-mskjoopy/agent-50c3d4af

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

A bare GitHub repo URL captured in the Brain was already saved as a link and cloned in the background — but nothing said so, and nothing followed. Both capture boxes (the Quick Capture dashboard widget and Brain → Inbox) now recognize a repo URL, name the owner/repo that will be cloned, and offer two opt-in checkboxes:

  • Scan for malware — the same read-only /do:scan audit as the Links tab's Scan button, ending in a CLEAN / CAUTION / DANGEROUS report you open from the link.
  • Study for PortOS ideas — a repo-study agent that reads the clone for implementation ideas worth adopting and files the good ones into whichever tracker this install is configured for (GitHub issues / PLAN.md / GitLab / JIRA).

Both preferences stick, so "always scan what I clone" is a one-time tick. The agents dispatch only after the clone succeeds, and the request is persisted on the link — so a Clone/Retry after a failed clone still honors it. Re-pasting a repo you already saved queues nothing, since there is no new clone to read.

The study prompt is clean-room by contract: never execute anything from the clone, never edit it, read its LICENSE first, and describe techniques in its own words rather than copying code.

Supporting changes

  • declaresNoCommitCriterion now also accepts the workTracker marker a tracker-filing dispatch already stamps. Previously the only way for a one-off tracker-filing run to earn the no-commit exemption was to claim a scheduled task type — which would also enroll it in taskSchedule's per-type consecutive-failure ledger and eventually auto-park (and notify about) a "type" no schedule owns.
  • resolveTrackerFilingBlock moves from cosTaskGenerator.js into lib/workTracker.js, next to the wording table it reads, so all three tracker-filing dispatch paths share one resolution instead of a third hand-copy.
  • parseGitHubUrl moves to server/lib/githubRepoUrl.js with a client mirror (parity pinned by a test), so the capture boxes predict "this will be cloned" exactly as the server decides it.
  • Security fix found by the review gate: that parser matched owner/repo as [^/]+, so https://github.com/../evil parsed to owner .. and join(reposDir, owner, repo) resolved outside the managed clone root — a path since handed to an agent as its scan directory. Segments are now anchored to GitHub's real character sets, dot segments rejected, and the host anchored so evil.example.com/github.com/o/r is no longer read as a repo.
  • Fixes the Links-tab Scan button recording nothing on the link, which left it re-arming into a duplicate-task 409 after a reload.

Test plan

  • cd server && npm test — 1200 files pass; the 51 failing files are the pre-existing Postgres-backed suites (no DB provisioned in this worktree), byte-identical to the pre-change baseline.
  • cd client && npm test — 558 files / 6590 tests pass. biome lint clean, npm run build clean.
  • New coverage:
    • server/lib/githubRepoUrl.test.js — parse shapes, deep links, .git stripping, and the path-safety cases (dot segments, percent-encoding, foreign hosts, clone-path containment).
    • server/lib/githubRepoUrl.mirror.test.js — server↔client parity; verified it fails on a planted divergence.
    • server/lib/repoIntakeActions.{test,mirror.test}.js — normalizer semantics (null on nothing-ticked, literal-true only, unknown keys dropped) and client↔server action-key parity.
    • server/services/repoIntake.test.js — task shape for both actions, tracker resolution across github/plan/origin-failure, the link patch each returns, and one action failing without taking the other down.
    • server/services/taskTypeHooks.noCommit.test.js — the generalized no-commit gate, including that the flag alone does not exempt and auto is not a resolved tracker.
    • server/services/brain.test.js — intake threading: stored on the link, normalized, dropped for non-GitHub URLs, and not re-queued on a re-paste.
    • client/src/components/QuickBrainCapture.test.jsx — options appear only for a bare repo URL (not for prose-wrapped or non-repo github.com URLs), stick across mounts, and are omitted when the text is no longer a repo URL at submit time.

…agents

A bare GitHub repo URL captured in the Brain was already saved as a link and
cloned in the background, but nothing said so and nothing followed. Both capture
boxes now name the owner/repo that will be cloned and offer two sticky opt-ins:

  - Scan for malware — the same read-only /do:scan audit as the Links tab's Scan
    button, now sharing one task shape via services/repoIntake.js
  - Study for PortOS ideas — a repo-study run that reads the clone for ideas
    worth adopting and files them into the configured work tracker

Both dispatch only after the clone succeeds, and the request is persisted on the
link so a Clone/Retry after a failed clone still honors it.

The study prompt is clean-room by contract: never execute anything from the
clone, never edit it, read its LICENSE first, describe techniques rather than
copying code.

Two supporting changes at the shared layer:

  - declaresNoCommitCriterion now also accepts the `workTracker` marker a
    tracker-filing dispatch stamps, so a one-off run reaches the no-commit
    exemption without claiming a scheduled task type — which would have enrolled
    it in taskSchedule's per-type failure ledger and auto-parked a type no
    schedule owns.
  - resolveTrackerFilingBlock moves from cosTaskGenerator.js into
    lib/workTracker.js, next to the wording table it reads, so all three
    tracker-filing dispatch paths share one resolution instead of copying it.

Also fixes the Links-tab Scan button recording nothing on the link, which left
it re-arming into a duplicate-task 409 after a reload.
parseGitHubUrl matched both segments as `[^/]+`, so a crafted URL parsed to a
dot segment: `https://github.com/../evil` yielded owner `..`, and githubCloner's
`join(reposDir, owner, repo)` then resolved OUTSIDE the managed clone root
(`/data/repos/../evil` → `/data/evil`). `github.com/foo/..` collapsed to the
repos root itself. That localPath is persisted on the link and handed to an
agent as the directory to scan or study, so a bad parse aims a run at an
arbitrary directory.

Anchor both segments to the character sets GitHub actually allows (owner: a
login, alphanumeric with internal hyphens; repo: alphanumerics plus _ . -),
reject the `.`/`..` repo names that class still admits, and anchor the host so
`https://evil.example.com/github.com/o/r` is no longer read as a GitHub repo.

Found by the pre-PR review gate. Pre-existing in githubCloner.js, but this
branch promotes that parser to the shared lib/ rule and adds the agent-facing
consumer, so it lands here.
@atomantic
atomantic merged commit 786c310 into main Aug 8, 2026
6 checks passed
@atomantic
atomantic deleted the cos/task-mskjoopy/agent-50c3d4af branch August 8, 2026 16:47
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