open-swe/agent/middleware/pr_verdict_guard.py

255 lines
9.3 KiB
Python
Raw Normal View History

feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
"""Block shell fallbacks that submit PR review verdicts outside publish_review."""
from __future__ import annotations
import json
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
import os
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
import re
import shlex
from collections.abc import Awaitable, Callable, Mapping
from typing import Any
from langchain.agents.middleware.types import AgentMiddleware, AgentState
from langchain_core.messages import ToolMessage
from langgraph.prebuilt.tool_node import ToolCallRequest
from langgraph.types import Command
_SHELL_SEPARATORS = {";", "&&", "||", "|", "&"}
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
_SHELL_EXECUTABLES = {"bash", "dash", "sh", "zsh"}
_MAX_SHELL_EXPANSION_DEPTH = 3
_SHELL_EXPANSION_DEPTH_LIMIT_TOKEN = "__pr_verdict_guard_shell_expansion_depth_limit__"
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
_GITHUB_REVIEWS_ENDPOINT = re.compile(
r"(?:^|/)repos/[^/\s]+/[^/\s]+/pulls/\d+/reviews(?:/\d+/events)?/?$"
)
_GITHUB_REVIEWS_URL = re.compile(
r"https://api\.github\.com/repos/[^/\s]+/[^/\s]+/pulls/\d+/reviews(?:/\d+/events)?/?"
)
_VERDICT_EVENT_RE = re.compile(r"\bevent\b.{0,4}?(APPROVE|REQUEST_CHANGES)", re.IGNORECASE)
_GH_PR_REVIEW_VERDICT_FLAGS = {"--approve", "-a", "--request-changes", "-r"}
_GH_PR_REVIEW_VERDICT_PREFIXES = ("--approve=", "--request-changes=")
_BLOCK_ERROR = (
"PR review verdicts (approve / request changes) must go through the "
"publish_review tool, which enforces verdict authorization, self-review "
"handling, and reviewer bookkeeping. Do not fall back to gh pr review, "
"gh api .../reviews, curl, or another direct review-submission path. If a "
"verdict was requested, call publish_review(verdict=...); otherwise "
"publish a comment review."
)
def _tool_name(request: ToolCallRequest) -> str | None:
tool_call = getattr(request, "tool_call", None)
if isinstance(tool_call, Mapping):
name = tool_call.get("name")
return name if isinstance(name, str) else None
return None
def _tool_args(request: ToolCallRequest) -> dict[str, Any]:
tool_call = getattr(request, "tool_call", None)
args = tool_call.get("args") if isinstance(tool_call, Mapping) else None
return dict(args) if isinstance(args, Mapping) else {}
def _tool_call_id(request: ToolCallRequest) -> str | None:
tool_call = getattr(request, "tool_call", None)
if isinstance(tool_call, Mapping):
value = tool_call.get("id")
return value if isinstance(value, str) else None
return None
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
def _split_shell_tokens(command: str) -> list[str]:
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
try:
return shlex.split(command, posix=True)
except ValueError:
return command.split()
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
def _executable_name(token: str) -> str:
return os.path.basename(token.strip("'\""))
def _shell_command_argument(tokens: list[str], shell_index: int) -> str | None:
for index, token in enumerate(tokens[shell_index + 1 :], start=shell_index + 1):
if token in _SHELL_SEPARATORS:
return None
if token == "-c" or (
token.startswith("-") and not token.startswith("--") and "c" in token[1:]
):
_, _, glued = token[1:].partition("c")
if glued:
return glued
if index + 1 < len(tokens) and tokens[index + 1] not in _SHELL_SEPARATORS:
return tokens[index + 1]
return None
return None
def _has_nested_shell_command(tokens: list[str]) -> bool:
return any(
_executable_name(token) in _SHELL_EXECUTABLES
and _shell_command_argument(tokens, index) is not None
for index, token in enumerate(tokens)
)
def _expand_nested_shell_tokens(tokens: list[str], depth: int = 0) -> list[str]:
if depth >= _MAX_SHELL_EXPANSION_DEPTH:
if _has_nested_shell_command(tokens):
return [*tokens, _SHELL_EXPANSION_DEPTH_LIMIT_TOKEN]
return tokens
expanded = list(tokens)
for index, token in enumerate(tokens):
if _executable_name(token) not in _SHELL_EXECUTABLES:
continue
inner_command = _shell_command_argument(tokens, index)
if inner_command is None:
continue
expanded.extend(_expand_nested_shell_tokens(_split_shell_tokens(inner_command), depth + 1))
return expanded
def _shell_tokens(command: str) -> list[str]:
return _expand_nested_shell_tokens(_split_shell_tokens(command))
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
def _subtokens_after(tokens: list[str], index: int) -> list[str]:
subtokens: list[str] = []
for token in tokens[index + 1 :]:
if token in _SHELL_SEPARATORS:
break
subtokens.append(token)
return subtokens
def _contains_gh_pr_review_verdict(tokens: list[str]) -> bool:
for index, token in enumerate(tokens):
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
if _executable_name(token) != "gh":
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
continue
subtokens = _subtokens_after(tokens, index)
is_pr_review = any(
subtoken == "pr" and subtokens[offset + 1] == "review"
for offset, subtoken in enumerate(subtokens[:-1])
)
if not is_pr_review:
continue
for subtoken in subtokens:
if subtoken in _GH_PR_REVIEW_VERDICT_FLAGS or subtoken.startswith(
_GH_PR_REVIEW_VERDICT_PREFIXES
):
return True
return False
def _contains_gh_api_review_verdict(tokens: list[str]) -> bool:
for index, token in enumerate(tokens):
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
if _executable_name(token) != "gh":
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
continue
subtokens = _subtokens_after(tokens, index)
if "api" not in subtokens:
continue
targets_reviews = any(
_GITHUB_REVIEWS_ENDPOINT.search(subtoken.strip("'\""))
or _GITHUB_REVIEWS_URL.search(subtoken)
for subtoken in subtokens
)
if not targets_reviews:
continue
if any(_VERDICT_EVENT_RE.search(subtoken) for subtoken in subtokens):
return True
return False
def _contains_direct_review_verdict(tokens: list[str]) -> bool:
for index, token in enumerate(tokens):
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
if _executable_name(token) != "curl":
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
continue
subtokens = _subtokens_after(tokens, index)
if not any(_GITHUB_REVIEWS_URL.search(subtoken) for subtoken in subtokens):
continue
if any(_VERDICT_EVENT_RE.search(subtoken) for subtoken in subtokens):
return True
return False
def is_pr_verdict_fallback_command(command: str) -> bool:
"""Return True when *command* submits a PR review verdict from the shell.
Detection is literal-token matching — it catches the primary vectors
(``gh pr review --approve/-a/--request-changes/-r``, ``gh api`` posting
``event=APPROVE|REQUEST_CHANGES`` to a ``/pulls/N/reviews`` endpoint, and
``curl`` to the reviews URL with a verdict event in the body) but is
intentionally fail-open: shell aliases, ``gh`` aliases, ``--input``
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
JSON-file bodies, and non-curl HTTP clients will not be blocked. The
``-c`` form of a known shell (``bash``/``dash``/``sh``/``zsh``, whether
space-separated or glued as ``-c'...'``) is expanded to a fixed depth and
quoted/path-prefixed executables are normalized, so those do not slip
through; other shells (``ash``/``busybox``/``fish``) and stdin-fed
programs (``... | bash``, here-strings, ``bash -s``) remain fail-open.
This is
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
acceptable because the threat model is an honest agent papering over a
``publish_review`` limitation or refusal, not an adversary trying to
bypass the guardrail. Plain ``gh pr review`` (no verdict flag) and
``--comment``/``-c`` are allowed — non-interactive ``gh`` cannot submit a
verdict without one of the blocked flags.
"""
tokens = _shell_tokens(command)
return (
feat(open-swe): port upstream clean batch (#1788, #1786, #1764, #1782, #1791, #1799) + guard hardening (#226) * Fix: Fix Insecure Direct Object Reference in slack_start_new_thread.py (#1788) Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> (cherry picked from commit 32e81f2979a7baf11fe387df59f7d13a31889c74) * Fix PR creation guard shell bypasses (#1786) Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> (cherry picked from commit 75fb8b487852003916c4984504a13ee7226b2ceb) * fix: add exc_info to swallowed exception in push re-review webhook (#1764) (cherry picked from commit ab85b372b4f37b7feb849054553daed10852a42c) * chore: clarify shared response image guidance (#1782) Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 2e8ff4b72f1148bb36c0c1181063a3abd78b15d0) * fix: match embedded review description background (#1791) Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> (cherry picked from commit 4ea2441ada1229bc414b02950d821786be2f7301) * fix: show current shared thread in sidebar (#1799) * fix: show current shared thread in sidebar Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * fix: preserve resolved active sidebar threads Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com> (cherry picked from commit a77c4e475643b4a55bb2f0c93c0aa2669014fbac) * chore: switch deferred items to landed in upstream-sync triage documentation and jsonl entries (fork PR #226). Signed-off-by: Adam Moussa <adam@seahavenind.com> * harden PR guards + sidebar after security review - Mirror upstream #1786's nested-shell / executable-normalization hardening into the fork-only pr_verdict_guard.py (verdict-gating is a real fork control), keeping it in parity with pr_creation_guard.py. - Close the glued short-flag bypass (bash -c'...') in BOTH guards: a shell's -c argument can be concatenated into the same argv token, which the space-separated -c detection missed. Diverges pr_creation_guard.py from upstream #1786 by design; to be upstreamed. - Gate the new #1799 sidebar active-thread refresh on ownership so a non-owner viewing a shared thread reads last-known state without persisting a metadata write (mirrors the is_owner gate on the single-thread read path). - Fix an F821 in the #1799 cherry-pick (Mapping import / concrete dict type). Guards remain intentionally fail-open per the honest-agent threat model; docstrings narrowed to name the residual exotic-shell / stdin-fed vectors. --------- Signed-off-by: Adam Moussa <adam@seahavenind.com> Co-authored-by: corridor-security[bot] <203152403+corridor-security[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: langsmith-fleet[bot] <langsmith-fleet[bot]@users.noreply.github.com> Co-authored-by: Suraj Bayas <surajyou24@gmail.com> Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev> Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> Co-authored-by: Johannes du Plessis <johannes@langchain.dev> Co-authored-by: Johannes du Plessis <51395795+johannes117@users.noreply.github.com>
2026-07-24 18:49:53 -04:00
_SHELL_EXPANSION_DEPTH_LIMIT_TOKEN in tokens
or _contains_gh_pr_review_verdict(tokens)
feat(open-swe): explicit-request reviewer verdicts + shell verdict guard (#214) * feat(reviewer): explicit-request verdicts + shell verdict guard Mention-triggered reviews that explicitly ask for a verdict now submit a real APPROVE/REQUEST_CHANGES through publish_review; auto-reviews stay advisory (COMMENT). Authorization is enforced in code: publish_review honors a verdict only when the dispatching webhook set verdict_requested, which only the explicit-mention path does. - request_pr_review gains instructions (forwarded verbatim into an escaped requester_instructions data block) and request_verdict - self-review guard downgrades verdicts on Open SWE-authored PRs; stale APPROVEs are best-effort dismissed when later findings land - new PullRequestVerdictGuardMiddleware blocks gh pr review --approve/-a/--request-changes/-r, gh api, and curl verdict fallbacks on both the coding-agent and reviewer graphs - shared escape helper moved to agent/utils/prompt_data.py * fix(reviewer): harden verdict path against security-review findings Adversarial security review (detector fan-out + proof-or-kill verifier) of the verdict feature surfaced several verdict-integrity gaps; resolve the confirmed ones: - head-drift (high): a mid-run push moves the resolved head, so an APPROVE could anchor to an unreviewed commit. Downgrade any verdict to a comment when the resolved head differs from the reviewed head (verdict_ignored reason head_moved); the push's own re-review submits a fresh verdict. - self-review fail-open: downgrade to comment when the PR author cannot be confirmed (author_unknown), and compare bot logins case-insensitively. - verdict_submitted now reflects GitHub's returned review state, not just the event we asked for, so a coerced APPROVE isn't reported as submitted. - an authorized verdict whose findings all anchor outside the diff now posts as a bodied review with zero inline comments instead of failing. - add finding_reply to the shared data-block escape tag superset.
2026-07-20 15:28:00 -04:00
or _contains_gh_api_review_verdict(tokens)
or _contains_direct_review_verdict(tokens)
)
def _blocked_tool_message(request: ToolCallRequest, command: str) -> ToolMessage:
content = {
"status": "error",
"error_type": "PullRequestVerdictFallbackBlocked",
"code": "pr_verdict_fallback_blocked",
"recoverable_by_agent": False,
"error": _BLOCK_ERROR,
"blocked_command": command,
}
return ToolMessage(
content=json.dumps(content),
tool_call_id=_tool_call_id(request),
status="error",
)
class PullRequestVerdictGuardMiddleware(AgentMiddleware):
"""Keep APPROVE/REQUEST_CHANGES submissions on the guarded publish_review path."""
state_schema = AgentState
def _blocked_message_for_request(self, request: ToolCallRequest) -> ToolMessage | None:
if _tool_name(request) != "execute":
return None
command = _tool_args(request).get("command")
if not isinstance(command, str) or not is_pr_verdict_fallback_command(command):
return None
return _blocked_tool_message(request, command)
def wrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command],
) -> ToolMessage | Command:
blocked = self._blocked_message_for_request(request)
if blocked is not None:
return blocked
return handler(request)
async def awrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]],
) -> ToolMessage | Command:
blocked = self._blocked_message_for_request(request)
if blocked is not None:
return blocked
return await handler(request)