Skip to content

Async approvals, justification settings#19

Open
d-mo wants to merge 8 commits intomainfrom
async-approvals
Open

Async approvals, justification settings#19
d-mo wants to merge 8 commits intomainfrom
async-approvals

Conversation

@d-mo
Copy link
Contributor

@d-mo d-mo commented Feb 17, 2026

Note

High Risk
Two high-severity security issues were found (approval token exposure and prompt delimiter injection) that should be fixed before merge.

Overview
Adds async approval mode with polling, server-side justification enforcement, and OpenCode agent support, plus related docs and tests.
Also updates agent scripts and log sentinel handling for more reliable execution checks.

Written by Preloop PR Reviewer for commit 5ae66b2. Updates automatically on new commits.

@d-mo d-mo changed the title Async approvals Async approvals, justification settings Feb 17, 2026
- Add post-exec debug sleep trap to Gemini agent (parity with Codex)
- Remove hardcoded HOME=/root from Gemini (container.py handles it)
- New OpenCode agent: opencode run --non-interactive, JSON MCP config, provider-agnostic
- Register OpenCode in factory and __init__
- Add unit tests: 19 Codex, 17 Gemini, 26 OpenCode, 12 factory (84 total)
d-mo added 6 commits February 17, 2026 22:30
- Use exact-line match for FLOW_SUCCESS_SENTINEL to prevent false
  positives when agent greps/cats source files containing the sentinel
  string (flow_orchestrator.py, container.py)
- Bypass server-side justification enforcement during async re-execution
  of approved tools (_bypass_approval_var) to avoid rejecting replays
  that lack justification in tool_args
- Gate getUsers()/getTeams() in approval-policy-dialog behind
  _hasAdvancedApprovals() to avoid 404s in OSS single-user mode
- Add PolicyGenerationService using litellm for provider-agnostic LLM calls
- Add POST /api/v1/policies/generate endpoint (prompt-based)
- Add POST /api/v1/policies/generate-from-audit endpoint (audit-log-based)
- Add 'preloop policy generate' CLI command with --from-audit-logs, --file, --output flags
- Add 'preloop agents discover' CLI command scanning 8 AI agent configs
- Add policy-generate-dialog.ts frontend component
- Add generatePolicy/generatePolicyFromAudit API functions
- Add litellm>=1.50.0 dependency
- LLM prompts reuse existing MCP servers, approval policies, and tool rules
…low execution

- Add _get_project_id() helper to GitLabTracker (fixes AttributeError in create_commit_status, add/remove_mr_award_emoji)
- Fix discussion.notes access in create_mr_discussion for python-gitlab v4+ (notes is a manager, not a list)
- Use mr.changes.get instead of mr.changes for MR diff fetching (manager not callable in v4+)
- Add trigger_project_id (singular) to execution context so container.py git clone fallback works
…ault, DB lock, changelog

- Sentinel: add PRELOOP_AGENT_EXEC_START marker to suppress detection during
  prompt echo; restructure instruction so sentinel is not on its own line;
  add detailed [Sentinel] logging for diagnostics
- OpenCode: limit envsubst to $PRELOOP_MCP_URL and $PRELOOP_API_TOKEN to
  prevent $schema corruption; use raw prompt in single-quoted heredoc
- Agents: default AGENT_POST_EXEC_SLEEP to 0 (codex, gemini, opencode)
- Approvals: claim-then-execute pattern in get_approval_status to release
  FOR UPDATE lock before tool execution
- Changelog: fix tool name check_approval_status -> get_approval_status
- Tests: fix test_dynamic_fastmcp hanging (missing super()._list_tools mock)
…ing, desktop notifs, loading state

- ApprovalEvent: use naive datetime.utcnow for TIMESTAMP WITHOUT TIME ZONE column
- Proxied-tool wrapper: read justification from _justification_var instead of
  already-stripped arguments
- Policy generation endpoints: wrap sync LLM calls in asyncio.to_thread
- Desktop notifications: always emit for flow executions and approvals (remove
  isDesktopNotificationsEnabled gate)
- flow-view: wrap reference data loading in try/finally to prevent stuck loading
- Add 38 tests for PolicyGenerationService and generation endpoints
- Gemini agent: emit PRELOOP_AGENT_EXEC_START before CLI command so
  orchestrator sentinel detection works correctly
- OpenCode agent: same fix — emit PRELOOP_AGENT_EXEC_START before CLI command
- invitation-management-view: set isLoading=false when feature disabled
  (prevents infinite spinner), gate render() on featureEnabled
- team-management-view: gate render() on featureEnabled
- user-management-view: gate render() on featureEnabled
Copy link
Contributor

@preloop preloop bot left a comment

Choose a reason for hiding this comment

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

Preloop has reviewed this PR.

See the pinned summary comment below for the full review details and issue tracker.

"poll_interval_seconds": poll_interval,
"timeout_seconds": policy.timeout_seconds or 300,
"channels": notification_channels,
"approval_url": approval_url,
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH Security]

Affected files:

  • backend/preloop/services/approval_helper.py:343

Returning approval_url (which includes the approval token) to the calling agent in the async response leaks the bearer token back to the requester. That allows the agent to self-approve (or share the link), which defeats the approval boundary.

Recommendation:
Do not include the approval URL/token in tool responses. Return only request_id and metadata, and deliver the approval link exclusively via trusted notification channels or a server-side UI for authenticated approvers.


# Create prompt file to avoid shell escaping issues.
# The single-quoted heredoc delimiter preserves content literally.
cat > /tmp/prompt.txt << 'PROMPT_EOF'
Copy link
Contributor

Choose a reason for hiding this comment

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

[HIGH Security]

Affected files:

  • backend/preloop/agents/opencode.py:386

The prompt is written via a fixed heredoc delimiter. If the prompt contains a line PROMPT_EOF, the heredoc terminates early and the remaining prompt lines are interpreted as shell code, allowing command injection before OpenCode runs. Prompts can originate from external events.

Recommendation:
Use a randomized delimiter (e.g., UUID), or write the prompt via a safe mechanism (base64 decode, or a Python heredoc) so delimiter injection is impossible.

@preloop
Copy link
Contributor

preloop bot commented Feb 18, 2026

🔍 Preloop Code Review

Last Updated: 2026-02-18 02:33:44 UTC
Reviewing Commit: 5ae66b2
Review Status: ❌ Changes Requested


📝 Summary

This PR adds async approvals, per-tool justification enforcement, and OpenCode agent support. The direction is solid, but two high-severity security issues need to be addressed before merge.

✅ What Looks Good

  • Async approval polling uses row locking and cached results to prevent double execution.
  • OpenCode and Gemini agent scripts now align with the Codex debug/sentinel behavior.

⚠️ Issues Found

🟠 High Priority

[Should be addressed]

  • Security: Async approval response exposes approval_url (approval token) to the calling agent - backend/preloop/services/approval_helper.py:343
  • Security: Fixed heredoc delimiter allows prompt to terminate heredoc and inject shell commands in OpenCode script - backend/preloop/agents/opencode.py:386

✅ Resolved Issues

  • (none)

Progress: 0 of 2 issues addressed

This summary updates automatically on each review. Inline comments provide detailed feedback on specific lines.

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