mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
fix(open-swe): restrict Linear token attribution to comment author only
Split actor_email (comment_author only, feeds github_login for token attribution) from user_email (full fallback chain, for display/model). This ensures a PR is never opened as a non-actor (creator/assignee). Add test_resolve_github_token_linear_defaults_to_bot to lock the security-critical default: Linear + mapped login + no opt-in → bot.
This commit is contained in:
parent
b8f08a4d7b
commit
d201e361a9
2 changed files with 33 additions and 6 deletions
|
|
@ -40,12 +40,19 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
if not full_issue:
|
||||
full_issue = issue_data
|
||||
|
||||
user_email = None
|
||||
user_name = None
|
||||
# Actor email for token attribution: restricted to the comment author only.
|
||||
# The creator/assignee fallback chain is intentionally excluded here so a PR
|
||||
# is never opened as a non-actor.
|
||||
actor_email = None
|
||||
comment_author = issue_data.get("comment_author", {})
|
||||
if comment_author:
|
||||
user_email = comment_author.get("email")
|
||||
actor_email = comment_author.get("email")
|
||||
user_name = comment_author.get("name")
|
||||
|
||||
# User email with full fallback chain for display, model selection, and
|
||||
# @mention instructions.
|
||||
user_email = actor_email
|
||||
if not user_email:
|
||||
creator = full_issue.get("creator", {})
|
||||
if creator:
|
||||
|
|
@ -165,10 +172,11 @@ async def process_linear_issue( # noqa: PLR0912, PLR0915
|
|||
)
|
||||
content_blocks: list[dict[str, Any]] = [create_text_block(prompt)]
|
||||
|
||||
# Resolve the GitHub login from the Linear email via the same user-mapping
|
||||
# store Slack uses, so PRs open *as the triggering user* and the thread is
|
||||
# tagged for the dashboard.
|
||||
mapped_login = await webapp.resolve_login_from_email_async(user_email) if user_email else None
|
||||
# Resolve the GitHub login from the actor's Linear email via the same
|
||||
# user-mapping store Slack uses, so PRs open *as the triggering user* and the
|
||||
# thread is tagged for the dashboard. Restricted to the comment author so
|
||||
# token attribution never falls back to creator/assignee.
|
||||
mapped_login = await webapp.resolve_login_from_email_async(actor_email) if actor_email else None
|
||||
|
||||
image_model_override: tuple[str, str] | None = None
|
||||
if image_urls:
|
||||
|
|
|
|||
|
|
@ -234,6 +234,25 @@ def test_resolve_github_token_linear_optin_no_token_falls_back_to_bot_in_bot_onl
|
|||
assert (token, expires_at) == ("bot-tok", None)
|
||||
|
||||
|
||||
def test_resolve_github_token_linear_defaults_to_bot(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# DEFAULT (no author_prs_as_user opt-in): linear runs author as the app bot,
|
||||
# even when a valid per-user token and mapped login exist — so PRs can never
|
||||
# be opened as a non-actor (creator/assignee).
|
||||
_stub_dashboard_store(monkeypatch, token="user-tok")
|
||||
_set_profile(monkeypatch, author_prs_as_user=False)
|
||||
monkeypatch.setattr(auth, "is_bot_token_only_mode", lambda: False)
|
||||
|
||||
async def fake_bot(thread_id: str):
|
||||
return ("bot-tok", None)
|
||||
|
||||
monkeypatch.setattr(auth, "_resolve_bot_installation_token", fake_bot)
|
||||
|
||||
token, _ = asyncio.run(auth.resolve_github_token(_linear_config(), "t1"))
|
||||
assert token == "bot-tok"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source", ["github"])
|
||||
def test_resolve_github_token_bot_only_mode_non_slack_uses_bot(
|
||||
monkeypatch: pytest.MonkeyPatch, source: str
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue