Skip to content

Add Factory Droid agent-hook support#907

Open
lukeaus wants to merge 26 commits into
kenn-io:mainfrom
lukeaus:feat/droid-hook
Open

Add Factory Droid agent-hook support#907
lukeaus wants to merge 26 commits into
kenn-io:mainfrom
lukeaus:feat/droid-hook

Conversation

@lukeaus

@lukeaus lukeaus commented Jun 27, 2026

Copy link
Copy Markdown

Summary

  • Adds Factory Droid as a first-class roborev agent-hook --agent droid profile, backed by the shared agent-hook daemon and state machine.
  • Adds Factory Droid hook installation/dump support for ~/.factory/hooks.json / .factory/hooks.json, with Execute PreToolUse/PostToolUse hooks and a Stop hook that invoke roborev agent-hook run --agent droid.
  • Preserves Droid-specific [droid_hook] config, ROBOREV_DROID_HOOK_* env overrides, and Droid-friendly continuation instructions while sharing status/reset/daemon behavior with Codex and Claude Code.
  • Adds Factory Droid skills under .factory/skills plus docs and command references.
  • Closes Add first-class Factory Droid hook and skills support #906.

Testing needed

I do not have Factory Droid available locally. Please smoke test the latest PR head in a real Droid environment before merge.

@roborev-ci

roborev-ci Bot commented Jun 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (2c7a965)

Summary verdict: one Medium issue should be addressed; no High or Critical findings were reported.

Medium

  • Location: internal/droidhook/install.go:51
  • Problem: The Droid installer registers PreToolUse/PostToolUse hooks for the Execute tool, but the shared agenthook state machine skips tool events whose tool_name is not "Bash" (internal/agenthook/state.go:210, internal/agenthook/state.go:261). Droid Execute payloads will never seed commit baselines, count commits, or trigger PostToolUse reminders.
  • Fix: Teach the shared hook recorder to accept Droid’s Execute tool name, or normalize Droid hook payloads before posting them to the shared daemon. Add a test that sends a Droid PostToolUse payload with tool_name:"Execute".

Panel: ci_default_security | Synthesis: codex, 8s | Members: codex_default (codex/default, done, 4m27s), codex_security (codex/security, done, 3m37s) | Total: 8m12s

@lukeaus

lukeaus commented Jun 27, 2026

Copy link
Copy Markdown
Author

Resolved in 427b08a: the shared agenthook recorder now accepts Droid Execute tool events alongside Codex/Claude Bash events, so Droid PreToolUse/PostToolUse hooks seed commit baselines and count commits.

Added coverage for tool_name:"Execute" commit tracking and for non-shell tool names staying skipped.

Validation run:

  • go vet ./internal/agenthook
  • go test ./internal/agenthook -run "TestRecordToolUse|TestRecordPostToolUse"
  • go test ./internal/droidhook ./cmd/roborev -run "DroidHook"

@roborev-ci

roborev-ci Bot commented Jun 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (427b08a)

No Medium, High, or Critical findings were reported.

All reported findings were below Medium severity, and the security review found no issues.


Panel: ci_default_security | Synthesis: codex, 4s | Members: codex_default (codex/default, done, 3m39s), codex_security (codex/security, done, 2m3s) | Total: 5m46s

@wesm

wesm commented Jun 28, 2026

Copy link
Copy Markdown
Member

working on this

@wesm

wesm commented Jun 28, 2026

Copy link
Copy Markdown
Member

Posted by Codex on Wes's behalf.

I pushed a refactor/fix pass to this PR, preserving your latest commit 427b08a and adding follow-up commits on top. The user-facing hook surface is now roborev agent-hook --agent droid rather than a separate roborev droid-hook command.

Could you please test this against a real Factory Droid install? I do not have Droid available locally. The specific smoke test I want is:

  1. Pull the latest PR head.
  2. Run roborev agent-hook install --agent droid --dry-run, then roborev agent-hook install --agent droid.
  3. Inspect ~/.factory/hooks.json (or .factory/hooks.json with --scope project) and confirm it has PreToolUse/PostToolUse entries for Execute plus Stop, all invoking roborev agent-hook run --agent droid.
  4. In a real Droid session, verify Stop still fails open when not triggered and blocks with the configured instruction when review thresholds are met.
  5. If possible, set a small Droid commit_threshold and verify an Execute git commit is counted.

Please report back with the Factory Droid version/context you tested and any mismatch in hook payload shape or output JSON.

@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (9a2b76a)

No issues found.


Panel: ci_default_security | Synthesis: codex | Members: codex_default (codex/default, done, 6m37s), codex_security (codex/security, done, 3m1s) | Total: 9m38s

@wesm wesm changed the title Add first-class Factory Droid hook support Add Factory Droid agent-hook support Jun 28, 2026
@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (3665e65)

No Medium, High, or Critical findings were reported.

Security review found no concrete vulnerabilities; the only reported issue was Low severity and omitted per filtering rules.


Panel: ci_default_security | Synthesis: codex, 5s | Members: codex_default (codex/default, done, 4m23s), codex_security (codex/security, done, 2m52s) | Total: 7m20s

@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (f158238)

Summary verdict: Changes need follow-up before merge due to broken Droid skill behavior and misleading skill status output.

Medium

  • internal/skills/droid/roborev-lookahead-review/SKILL.md:52
    The new Droid lookahead skill instructs agents to run roborev review --type lookahead, but roborev review only accepts security and design, so this skill will always fail.
    Fix: Implement a lookahead review type end-to-end, or remove/change these lookahead skills to use a supported command.

  • cmd/roborev/skills.go:57
    roborev skills treats every skill from the union returned by ListSkills as applicable to every agent. The new Droid-only lookahead skills will be shown as missing for Claude/Codex even though skills install cannot install them there.
    Fix: Track per-skill supported agents and render/assert status only for agents that embed that skill, or add equivalent lookahead skill files for Claude/Codex.


Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 4m50s), codex_security (codex/security, done, 2m35s) | Total: 7m32s

@wesm

wesm commented Jun 28, 2026

Copy link
Copy Markdown
Member

looking at this

lukeaus and others added 13 commits June 28, 2026 13:31
Introduce roborev droid-hook run, a Droid-native entrypoint that delegates to
the shared internal/agenthook state machine and daemon so Factory Droid behaves
on par with the existing Codex/Claude agent-hook integration.

- internal/config: add [droid_hook] section mirroring [agent_hook] with Droid
  defaults (turn=5, commit=0, failed_review=4, Droid-flavored instruction).
- internal/droidhook: ResolveOptions reading [droid_hook] + ROBOREV_DROID_HOOK_*
  env overrides, producing agenthook.Options for the shared daemon.
- cmd/roborev: extract shared runHook helper from runAgentHook; add the
  droid-hook run subcommand; register droid-hook on the root command.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Add roborev droid-hook install and dump, which write and preview the Factory
Droid hooks.json entries (PreToolUse/PostToolUse on Execute, matcher-less Stop)
mirroring the agent-hook install for Codex/Claude. Factory's hooks.json uses the
same JSON shape as Codex/Claude, so the install logic is shared rather than
duplicated.

- internal/agenthook: export a runner-parameterized install API
  (InstallSpec, InstallSpecs, PlanSpecs, MarshalJSONConfig,
  ResolveHookCommandWithRunner, TranslateBinaryNotice) so any integration
  sharing the JSON hook format can reuse it. The runner string threads through
  the upsert chain so stale-command detection is scoped per integration
  (agent-hook run vs droid-hook run are disjoint and never clobber each other).
- internal/droidhook: install.go with DroidSpecs, RunInstall/RunDump,
  user/project scope resolution (~/.factory/hooks.json or .factory/hooks.json),
  and the droid-hook run command suffix.
- cmd/roborev: droid-hook install/dump subcommands with --command, --binary
  (install only), --config, --scope, --timeout, --dry-run (install only).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Ship the full roborev skill set for Factory Droid alongside the existing
Claude Code and Codex sets, so Droid users get the same review/fix/refine/respond
workflows. Droid skills install to ~/.factory/skills (Factory's personal skills
location) and are skipped when ~/.factory is absent, so the install stays opt-in
for Factory users.

The Droid skills are derived from the Codex skills (agent-agnostic: synchronous
--wait, no Claude-specific Task tool) with two Factory-specific adaptations:
slash invocation (/roborev-X, matching Factory's /skill-name convention) and
AGENTS.md (Factory's project-instructions file) instead of the Codex $roborev-X
and CLAUDE.md forms.

- internal/skills: add AgentDroid spec (.factory config dir, droid embed),
  embed the 7 droid SKILL.md files, and extend skills_test.go (Droid in
  agentCases, createMockSkill resolves config dir per agent, IsInstalled
  coverage, and guards for the Droid adaptations + install location).
- cmd/roborev: surface Droid in the skills status/install display, the
  quickstart skills check, and the post-update skill refresh trigger.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Complete the Factory Droid hook integration with session inspection commands
and user-facing documentation, and validate the full path end-to-end.

- cmd/roborev: add droid-hook status and reset, which delegate to the shared
  agenthook daemon (session state is integration-agnostic, keyed by session_id),
  plus a subcommand registration test.
- docs: add docs/droid-hook.md (the Factory Droid counterpart to agent-hook.md,
  covering the loop, quick start, runtime model, configuration, and session
  inspection), and surface droid-hook in README.md, docs/commands.md, and
  docs/index.md.

End-to-end validation (built binary, isolated data dir): droid-hook dump emits
the Factory hooks.json shape (PreToolUse/PostToolUse on Execute + Stop);
install --dry-run auto-resolves the binary and reports the target path; run
reaches the shared daemon, records sessions (count increments per Stop, repo
HEAD and branch resolved from cwd), and fails open ({}) when not triggered;
status reports session counters. The block-when-triggered path is covered by
the Phase 1 unit test.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Allow the shared agenthook recorder to treat Factory Droid Execute events as shell tool events so Droid PreToolUse/PostToolUse hooks seed commit baselines and count commits like Codex/Claude Bash hooks.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Factory Droid was added as a parallel droid-hook command even though its runtime path already reused the agenthook daemon and state machine. Keeping a second unshipped hook surface would make the CLI and package boundaries look more different than the behavior really is.

This makes Droid a third agent-hook profile instead: installs write Factory hooks that invoke agent-hook run --agent droid, while Droid-specific config, env vars, scope handling, and the Execute matcher stay explicit under agenthook. Commit tracking now treats Execute as the Droid shell-command tool so commit-threshold reminders can work through the shared state path.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
The Droid hook command surface was folded into agent-hook, but some installs from this unmerged branch may already have Factory hooks that invoke droid-hook run. Leaving those commands in hooks.json would keep calling a deleted command on every Droid hook event.

Treat the legacy Droid runner as replaceable only when planning the Droid agent-hook install, and tighten runner matching so the broader Codex/Claude agent-hook runner does not claim agent-hook run --agent droid entries.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
Droid now uses the shared agent-hook command with a longer runner suffix, agent-hook run --agent droid. The stale-command matcher still used substring matching, so a plain Codex or Claude install could mistake that Droid runner for its own agent-hook run entry and replace it in shared hook files.

Match the runner boundary instead of any substring so plain agent-hook installs and Droid profile installs remain disjoint. This intentionally does not add any legacy droid-hook migration behavior.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
The runner matcher needs to do two things at once: keep plain agent-hook installs from claiming the Droid profile, and still recognize valid stale hook commands that include supported run flags. Matching only end-of-string or quotes fixed the first case but rejected documented flags such as --turn-threshold and --config.

Accept whitespace-delimited runner suffix flags, while treating --agent droid as a different profile when matching the plain agent-hook runner. This keeps stale binary-path replacement working without collapsing Droid and non-Droid hook entries together.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
Droid hook installs use the shared agent-hook run command, but the Droid profile is selected by flags rather than a distinct subcommand. Exact matching on agent-hook run --agent droid missed valid commands where flags appeared in a different order or used --agent=droid, leaving stale binary-path hook commands active after reinstall.

Classify the parsed run suffix for plain and Droid profiles so stale hook replacement accepts supported flag forms while keeping Codex/Claude installs from claiming Droid entries. This intentionally keeps legacy droid-hook migration behavior out of the branch because that command never shipped.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
Add roborev-lookahead-review and roborev-lookahead-review-branch Droid skills
to match the Claude/Codex skill sets added in kenn-io#904, resolving CI test failures
where Droid was expected to have 9 skills but only had 7.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The Droid lookahead skills are Droid-only entries, but invoking them failed because review --type only accepted security and design. Treat lookahead as an explicit review type with its own temporal-leakage prompt while reusing the normal review workflow config for agent/model selection.

Track supported agents for each embedded skill so roborev skills status does not report Droid-only skills as missing from Claude or Codex installations.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (dc9160a)

High severity issue found.

High

  • internal/agenthook/install.go:695 - --scope project writes Factory Droid hook commands into .factory/hooks.json, and the docs recommend committing that file. This creates a branch-controlled executable hook surface: a PR author can modify .factory/hooks.json to run arbitrary commands, which may execute with a reviewer's local privileges when they use Factory Droid on that branch.

    Suggested remediation: remove project-scope executable hook support, or keep Droid hooks user-scoped and use only non-executable repo config/allowlists for per-project opt-in. At minimum, do not document committing project hook command files unless Factory Droid has a trusted/pinned hook approval model outside branch-controlled content.


Panel: ci_default_security | Synthesis: codex, 8s | Members: codex_default (codex/default, done, 8m29s), codex_security (codex/security, done, 4m42s) | Total: 13m19s

After rebasing over the commit that introduced the lookahead review type, the Droid branch needs to follow the upstream workflow mapping and embedded skill coverage instead of carrying pre-rebase assumptions. Keep lookahead using its own workflow key, remove duplicate prompt fallback code, and make the skill metadata expectations match the now-shared Claude/Codex/Droid lookahead skills.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@wesm wesm force-pushed the feat/droid-hook branch from dc9160a to f67751d Compare June 28, 2026 18:52
wesm and others added 3 commits June 28, 2026 13:58
Project-scoped Factory Droid hooks live in repo-local .factory/hooks.json, which is executable configuration a PR can modify. Keeping roborev's installer and docs on that path would make a shareable project config look safe when it can run branch-controlled commands in a reviewer's local agent session.

Keep the Droid integration user-scoped only, reject project scope for install and dump, and document why repo-local Factory hook commands should not be committed.

Validation: added project-scope rejection tests and watched them fail before the implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
Droid project scope was rejected, but a caller could still point --config at .factory/hooks.json and create the same repo-local executable hook file. Validate explicit Droid config paths before install or dump so the user-scoped-only rule cannot be bypassed by spelling the project hook path directly.

Validation: added --config .factory/hooks.json rejection tests and watched them fail before the implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
The project-path guard must not reject the legitimate user-scoped Factory hooks file when a user runs the command from their home directory. Check the resolved user hook path first, then reject only non-user targets that resolve to the current project's .factory/hooks.json.

Validation: added cwd-equals-HOME user-scope tests and watched them fail before the implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (b106a7c)

Medium-risk issue remains: Droid project hook protection can be bypassed from subdirectories or absolute config paths.

Medium

  • internal/agenthook/install.go:718 - The Droid project-config guard only rejects .factory/hooks.json relative to the current working directory. Running from a repo subdirectory with --config ../.factory/hooks.json or using an absolute path to the repo root’s .factory/hooks.json bypasses the check, so agent-hook install --agent droid can still write project-scoped Factory hooks despite explicitly disabling them.

    Suggested fix: Resolve the current git repo root and reject <repo-root>/.factory/hooks.json after converting the supplied config path to an absolute cleaned path. Add tests for subdirectory-relative and absolute paths.


Panel: ci_default_security | Synthesis: codex, 8s | Members: codex_default (codex/default, done, 4m46s), codex_security (codex/security, done, 3m49s) | Total: 8m43s

wesm and others added 2 commits June 28, 2026 14:15
Checking only the current directory left project hook writes reachable from repo subdirectories. Resolve the active git repo root and reject that root's .factory/hooks.json for Droid install and dump, while still allowing the resolved user-scoped Factory hooks path.

Validation: added subdirectory relative and absolute repo-root config path tests and watched them fail before the implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
A caller did not have to run from inside the target repository to write its .factory/hooks.json. Inspect the supplied config path itself: when it points at a git repository root's Factory hook file, reject it unless the path was already recognized as the user-scoped Factory hooks file.

Validation: added outside-repo relative and absolute target config path tests and watched them fail before the implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (6b6366e)

Medium-risk issue found; security review found no additional concrete exploit path.

Medium

  • internal/agenthook/install.go:713
    Droid hook config path validation checks only the supplied path, but writeJSONConfig later resolves existing symlinks with filepath.EvalSymlinks. A symlinked --config path, or ~/.factory/hooks.json, can point at <repo>/.factory/hooks.json and still pass validation, causing roborev to write the project-scoped Factory config it is intended to reject.
    Fix: Validate the resolved symlink target and resolved parent directory before writing, and add a test covering a symlink to repo-root .factory/hooks.json.

Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 8m28s), codex_security (codex/security, done, 2m56s) | Total: 11m31s

Check HOME env var before os.UserHomeDir() so tests that override HOME
work on Windows, where os.UserHomeDir returns USERPROFILE instead of
HOME. This fixes the three Windows-only test failures:
TestDefaultDroidHooksPath, TestRunInstallDroidAllowsUserScopeWhenHomeIsCWD,
and TestRunDumpDroidAllowsUserScopeWhenHomeIsCWD.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@roborev-ci

roborev-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown

roborev: Combined Review (82d871a)

Medium findings remain; no High or Critical issues were reported.

Medium

  • internal/agenthook/install.go:718 - Droid hook path validation only checks the lexical path, but writeJSONConfig later follows symlinks. An existing --config symlink pointing to <repo>/.factory/hooks.json can still write project-scoped Factory hooks despite the explicit ban. Resolve symlinks before classification, or avoid following symlinks for Droid hook writes; add symlink coverage for repo .factory/hooks.json targets.

  • internal/skills/skills.go:261 - Droid hooks resolve ~/.factory/hooks.json using HOME first, but Droid skill installation/status/update still use os.UserHomeDir(). When HOME differs from USERPROFILE, hooks and skills can target different .factory directories, so Droid may be prompted to run /roborev-fix without the skill installed where Droid looks. Centralize per-agent config home resolution and make Droid skills use the same HOME-first logic as DefaultDroidHooksPath.


Panel: ci_default_security | Synthesis: codex, 10s | Members: codex_default (codex/default, done, 5m25s), codex_security (codex/security, done, 5m34s) | Total: 11m9s

Droid hook installs rejected direct project config paths, but an existing symlink could still resolve to a repo-local .factory/hooks.json when the writer followed it. Factory Droid skills also need the same HOME-first config home that hook installs use, otherwise hook prompts can target a Droid setup that lacks the roborev skills.

Validate existing symlink targets before Droid hook writes and route Droid skill install, update, status, and installed checks through one agent-aware home resolver.

Validation: added symlink-target and HOME-split regression tests and watched them fail before implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

roborev: Combined Review (5a4cc4a)

Medium: one actionable issue remains.

Medium

  • internal/agenthook/install.go:728 - Droid hook path validation only evaluates the full hooks.json path when it already exists. If an existing parent directory is a symlink to a repo’s .factory directory and hooks.json does not exist yet, validation passes and writeJSONConfig can create the project-scoped Factory hook file anyway. Resolve the longest existing parent directory, append the remaining path, and run the project-scope checks on that resolved candidate before writing.

Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 6m8s), codex_security (codex/security, done, 2m54s) | Total: 9m9s

validateDroidHooksPath used filepath.EvalSymlinks on the full hooks.json
path, which only works when the file already exists. If a parent directory
(e.g., ~/.factory) was a symlink to a repo's .factory directory and
hooks.json did not exist yet, validation passed and writeJSONConfig could
create a project-scoped Factory hook file via the symlink.

Replace evalExistingPath with evalExistingParentPath, which walks up to
the longest existing ancestor, resolves symlinks on that portion, and
appends the remaining path components. This catches symlinked parents
even when the target file has not been created yet.

Add tests for both RunInstall and RunDump covering the symlinked-parent
scenario.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@lukeaus

lukeaus commented Jun 29, 2026

Copy link
Copy Markdown
Author

Resolved in 96a945b: replaced evalExistingPath with evalExistingParentPath, which walks up to the longest existing ancestor, resolves symlinks on that portion, then appends the remaining path components. This catches symlinked parent directories even when hooks.json does not exist yet.

Added tests for both RunInstall and RunDump covering the symlinked-parent-to-repo-.factory scenario.

@roborev-ci

roborev-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

roborev: Combined Review (96a945b)

Code is mostly clean, with one medium test portability issue to fix.

Medium

  • internal/agenthook/install_test.go:423
    The new symlink-parent tests require os.Symlink to succeed, unlike the adjacent symlink test that skips when symlinks are unavailable. This can make the package test fail on Windows or restricted environments. Use the same err := os.Symlink(...); if err != nil { t.Skipf(...) } pattern for both symlinked-parent tests.

Panel: ci_default_security | Synthesis: codex, 6s | Members: codex_default (codex/default, done, 10m1s), codex_security (codex/security, done, 5m28s) | Total: 15m35s

@wesm

wesm commented Jun 29, 2026

Copy link
Copy Markdown
Member

looking

Some Windows and restricted test environments cannot create symlinks. The new symlinked-parent regression tests should behave like the existing symlink coverage by skipping when setup cannot create the fixture, rather than failing before exercising roborev behavior.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@lukeaus

lukeaus commented Jun 29, 2026

Copy link
Copy Markdown
Author

Resolved in 37d5879: both symlinked-parent tests now skip with t.Skipf when os.Symlink fails, matching the pattern used by the adjacent symlink coverage.

@roborev-ci

roborev-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

roborev: Combined Review (37d5879)

Medium-risk issue found: Droid hook path rejection can be bypassed on Windows due to case-sensitive checks.

Medium

  • Location: internal/agenthook/install.go:745
  • Problem: Droid project-hook path rejection is case-sensitive, so on Windows --config .Factory\hooks.json or differently cased absolute paths can bypass the .factory/hooks.json checks and still install repo-local Factory hooks.
  • Fix: Use Windows-aware path/base-name comparisons, such as strings.EqualFold on Windows, and add tests for mixed-case .factory/hooks.json paths.

Panel: ci_default_security | Synthesis: codex, 6s | Members: codex_default (codex/default, done, 6m40s), codex_security (codex/security, done, 2m58s) | Total: 9m44s

@wesm

wesm commented Jun 29, 2026

Copy link
Copy Markdown
Member

working on this

Droid hook path validation must follow Windows path semantics for project-scope detection. Case-sensitive string checks let mixed-case spellings of .factory/hooks.json bypass the user-scoped-only guard on case-insensitive filesystems.

Route Droid path and reserved-name comparisons through a Windows-aware comparison helper and cover mixed-case relative and absolute project config paths.

Validation: added mixed-case Droid project-path tests and watched them fail before implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

roborev: Combined Review (2abb346)

High-severity issue found: the Droid fix skill includes an unsafe shell command pattern for review-derived text.

High

  • internal/skills/droid/roborev-fix/SKILL.md:159
    The Droid /roborev-fix skill tells the agent to pass a review-derived summary inside a double-quoted shell argument:

    roborev comment --commenter roborev-fix --job <job_id> "<summary of changes>"

    Because the summary can include attacker-controlled review text or filenames, shell expansions could execute before roborev comment runs. Replace this with a safer input mechanism such as a quoted heredoc, temp file, stdin, or a dedicated message-file flag, and avoid embedding review-derived content directly in shell arguments.


Panel: ci_default_security | Synthesis: codex, 7s | Members: codex_default (codex/default, done, 6m55s), codex_security (codex/security, done, 5m15s) | Total: 12m17s

The fix skills ask agents to summarize review findings back through roborev comment. Passing that dynamic text as a quoted shell argument leaves room for shell expansion if review-derived filenames or text are copied into the summary.

Match the safer refine-skill pattern by documenting quoted heredoc input for roborev comment, and keep examples from teaching inline dynamic shell arguments.

Validation: added embedded fix-skill regression coverage and watched it fail before implementation.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

roborev: Combined Review (2f79786)

High severity finding: the new Droid skill templates interpolate user-controlled refs into shell commands without safe quoting, enabling command injection.

High

  • internal/skills/droid/roborev-review-branch/SKILL.md:39
    The Droid review/design/lookahead branch and commit skills insert branch/tag/commit refs directly into command templates such as git rev-parse --verify -- <branch> and roborev review ... --base <branch>. Git refs can contain shell metacharacters, so a malicious ref from a shared repo could execute shell code when Droid follows the template.

    Fix: update Droid skill templates to store refs in shell variables and quote expansions, for example git rev-parse --verify -- "$base" and roborev review --branch --wait --base "$base". For commit refs, validate with git rev-parse --verify -- "${ref}^{commit}" and pass "$ref" to roborev review. Consider hardening existing non-Droid templates similarly.

Low-severity findings were omitted per instruction.


Panel: ci_default_security | Synthesis: codex, 9s | Members: codex_default (codex/default, done, 8m55s), codex_security (codex/security, done, 4m55s) | Total: 13m59s

@wesm

wesm commented Jun 29, 2026

Copy link
Copy Markdown
Member

accepted risk

Windows CI can check out embedded markdown with CRLF line endings, which made the heredoc regression test fail on prose that intentionally spans two lines. Normalize the embedded skill text in the assertion path so the test verifies the security-relevant template content instead of the platform checkout style.

Generated with Codex
Co-authored-by: Codex <codex@openai.com>
@roborev-ci

roborev-ci Bot commented Jun 29, 2026

Copy link
Copy Markdown

roborev: Combined Review (dd1572b)

High-level verdict: PR has one High security issue and one Medium portability issue that should be fixed before merge.

High

  • internal/skills/droid/roborev-review-branch/SKILL.md:39
    New Droid review/refine/design/lookahead skills interpolate user/ref-supplied branch or commit names directly into shell commands, including validation commands. Malicious ref names containing shell metacharacters could execute arbitrary commands when a maintainer asks Droid to review against them.
    Fix: Update all new Droid skills that consume refs to require safe argument handling: store raw values safely, then pass them only through quoted expansions such as git rev-parse --verify -- "$branch" and roborev review --branch --wait --base "$branch".

Medium

  • internal/agenthook/install.go:34
    Droid project hook path rejection only uses case-insensitive comparison on Windows. On common case-insensitive macOS volumes, a path like .Factory/hooks.JSON may resolve as .factory/hooks.json while bypassing validateDroidHooksPath, allowing a project-scoped config the feature intends to reject.
    Fix: Detect case sensitivity for the target filesystem/existing parent, or resolve path components with os.SameFile/directory entry checks instead of relying only on runtime.GOOS.

Panel: ci_default_security | Synthesis: codex, 9s | Members: codex_default (codex/default, done, 9m48s), codex_security (codex/security, done, 3m0s) | Total: 12m57s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Add first-class Factory Droid hook and skills support

2 participants