Skip to content

feat(cli)!: verify the comment author before issues post-comment edits - #7916

Merged
waynesun09 merged 2 commits into
mainfrom
issues-post-comment-author-check
Oct 1, 2026
Merged

waynesun09 merged 2 commits into
mainfrom
issues-post-comment-author-check

Conversation

@waynesun09

@waynesun09 waynesun09 commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

What

fullsend issues post-comment now edits an existing sticky comment only when the comment carries the marker and its author is exactly the identity the command posts as:

  • GitHub and GitLab: the login from forge.Client.GetAuthenticatedUser. It also resolves App installation tokens and Actions' GITHUB_TOKEN through GraphQL viewer.
  • Jira: the account ID from GET /myself.

Login shapes (such as a [bot] suffix) and display names are never trusted. A marker comment written by anyone else is ignored, and a new comment is posted.

Why

The command found its earlier comment by marker alone. Anyone who could comment could plant the marker: as body text on GitHub or GitLab, or on Jira as a marker property (or legacy body text) on their own comment. Every later run then wrote into the planted comment, or failed if the edit was refused. On Jira, comments are now filtered by author before both the property lookup and the legacy body-text fallback.

If the identity can't be resolved, the lookup retries twice and respects context cancellation. If it still fails, the command returns an error naming the cause and asking for a rerun, and posts or edits nothing. Posting a new comment instead would orphan the earlier one, leaving content that no later run updates.

BREAKING CHANGE

fullsend issues post-comment (GitHub, GitLab, Jira):

  1. Fails when it can't resolve the identity it posts as. It retries briefly, then exits non-zero with an error naming the cause, posting and editing nothing. Before, it never failed for this reason.
  2. No longer edits a marker comment written by a different identity. After a token rotation, a PAT → GitHub App migration or another identity change, the next run posts a new comment instead of updating the old one.
  3. --dry-run resolves the identity too. A dry run now fails if it can't resolve the posting identity. Before, it needed no identity.

The deprecated fullsend post-comment (sticky.Post) is unchanged here; #7884 tracks it.

Tests

internal/cli/issues_test.go:

  • GitHub, own comment: the poster's own comment is edited.
  • GitHub, planted comment: a planted marker from mallory, evil-review[bot], fullsend-ai-review or x-fullsend-ai-review[bot] gets a new comment.
  • GitHub, transient failure: a lookup failure followed by success retries and edits once, with no second comment.
  • GitHub, persistent failure: returns an error and creates or edits nothing.
  • GitHub, empty login and cancellation: an empty login errors, and a cancelled context stops the retry.
  • Jira, own comment: edited, in both the marker-property form and the legacy body-text form.
  • Jira, planted comment: a comment from another account (with the same display name) whose marker property or legacy body text is planted gets a new comment.
  • Jira, unresolvable identity: errors with nothing posted.
  • Jira, unit test: ownJiraComments matches by account ID and returns nothing for an empty identity.

Each of these mutations fails a test:

  • dropping the author check
  • posting a new comment when the identity can't be resolved
  • dropping the retry
  • dropping the Jira author filter
  • matching Jira by display name
  • dropping either empty-identity guard

The fakes support the change: FakeJiraClient gains GetMyself, Myself and MyselfError, and stamps its own account on new comments.

go test -race ./internal/cli/ ./internal/tracker/

Split out of #7243. Closes #7886. Closes #6236.

🤖 Generated with Claude Code

issues post-comment found its earlier sticky comment by marker alone.
Anyone who could comment could plant the marker (as body text, or on
Jira as a property on their own comment), and every later run then wrote
into the planted comment, or failed if the edit was refused.

An existing comment is now edited only when it carries the marker and
its author is exactly the identity the command posts as: the login from
forge.Client.GetAuthenticatedUser on GitHub and GitLab (which also
resolves App installation tokens and Actions' GITHUB_TOKEN through
GraphQL viewer), the account ID from GET /myself on Jira. Login shapes
and display names are never trusted. A planted comment is ignored and a
new comment posted.

The identity lookup retries twice (context-aware). If it still fails, the
command returns an error naming the cause, posts or edits nothing, and
says to rerun if the failure was transient or otherwise check that the
token can read its own identity. Posting a new comment instead would
orphan the earlier one. --dry-run resolves the identity too.

Help text and the user guide's trust model state the rule, drop the
"acceptable in trusted CI" caveat, and note that whoever can edit the
command's own comments is still trusted.

BREAKING CHANGE: issues post-comment fails, posting nothing, when it
 cannot resolve the identity it posts as, including under --dry-run; and
 it no longer edits a marker comment written by a different identity, so
 after a token rotation or App migration the next run posts a new
 comment instead of updating the old one.
Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09
waynesun09 requested a review from a team as a code owner October 1, 2026 14:49
@waynesun09
waynesun09 added this pull request to stack #7918 October 1, 2026 14:49
@waynesun09 waynesun09 changed the title issues post comment author check feat(cli)!: verify the comment author before issues post-comment edits Oct 1, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Verify comment authors before updating sticky issue comments

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Edit sticky comments only when their marker and author match the posting identity, preventing
 marker spoofing.
• Retry identity lookup; fail without posting or editing if identity remains unverified, including
 during dry runs.
• Document the revised trust model and test GitHub, GitLab, and Jira behavior.
Diagram

graph TD
  CLI["Post-comment command"] --> Clients["Tracker clients"] --> Verified{"Identity verified?"}
  Verified -- "No" --> Fail["Stop without changes"]
  Verified -- "Yes" --> Comments["Issue comments"] --> Match{"Own marked comment?"}
  Match -- "Yes" --> Edit["Edit comment"]
  Match -- "No" --> Create["Create comment"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Sign sticky markers
  • ➕ Could authenticate a marker without relying on comment authorship.
  • ➖ Requires secret distribution and rotation across installations.
  • ➖ Does not directly establish which existing comment the current posting account owns.

Recommendation: Use the PR's exact-author check: it relies on tracker-provided identity, covers existing markers without migration, and fails closed when identity is unavailable. Signed markers add operational complexity without a clear benefit for this ownership rule.

Files changed (7) +496 / -45

Bug fix (3) +129 / -21
issues.goRequire verified authorship before sticky-comment edits +110/-21

Require verified authorship before sticky-comment edits

• Resolves the posting identity with context-aware retries before listing comments, and rejects unresolved identities without writing. Matches forge comments by exact login and Jira comments by account ID before applying either Jira marker-property or legacy body-marker lookup; updates command help accordingly.

internal/cli/issues.go

forge_client.goExpose the authenticated forge login +7/-0

Expose the authenticated forge login

• Adds an AuthenticatedUser method that delegates to the underlying forge client for GitHub and GitLab.

internal/tracker/forge_client.go

jira_client.goExpose the authenticated Jira account ID +12/-0

Expose the authenticated Jira account ID

• Adds GetMyself to the Jira client interface and returns its account ID through AuthenticatedUser, avoiding display-name comparisons.

internal/tracker/jira_client.go

Tests (3) +348 / -8
issues_test.goTest author matching and fail-closed posting +285/-7

Test author matching and fail-closed posting

• Covers own-comment edits, planted markers, lookalike logins, Jira display-name collisions, and legacy Jira markers. Also tests transient and persistent identity failures, cancellation, empty identities, and dry-run verification.

internal/cli/issues_test.go

fake_jira.goGive fake Jira comments configurable author identities +25/-1

Give fake Jira comments configurable author identities

• Adds a default bot account, configurable posting identity, and a configurable GetMyself failure. Newly created fake comments now carry the identity used to post them.

internal/tracker/fake_jira.go

tracker_test.goTest tracker identity adapters +38/-0

Test tracker identity adapters

• Verifies forge login and Jira account-ID resolution, including upstream errors and a missing Jira account ID.

internal/tracker/tracker_test.go

Documentation (1) +19 / -16
issues-commands.mdDocument author-verified sticky comments +19/-16

Document author-verified sticky comments

• Explains exact-author matching, the remaining trust boundary, and failure behavior when identity cannot be resolved. Clarifies that dry runs can make identity-lookup API calls but do not write comments.

docs/guides/user/issues-commands.md

@qodo-code-review

qodo-code-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Retry guidance uses prose, not steps ✗ Dismissed
Description
The new trust-model paragraph tells readers to rerun the command or check token access in prose
rather than numbered steps. When identity lookup fails, those two troubleshooting actions appear
inside an explanatory paragraph instead of a procedure.
Code

docs/guides/user/issues-commands.md[R130-132]

+fails with an error naming the cause and posts or edits nothing. Rerun
+it if the failure was transient; otherwise check that the token can read
+its own identity. Posting a new comment instead would leave the earlier
Relevance

●● Moderate

Evidence is mixed: procedural lists were accepted, but recent similar requests to force numbered
formatting were rejected.

PR-#5778
PR-#7411

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The added guide text instructs readers to rerun the command or check token access in an unnumbered
paragraph, contrary to the checklist requirement for procedural content in guides.

docs/guides/user/issues-commands.md[129-133]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new identity-lookup failure guidance gives readers actions in prose, but guide procedures require numbered steps.

## Fix Focus Areas
- docs/guides/user/issues-commands.md[129-133]

## Recommended Fix
Keep the failure explanation, then present the retry and token-access checks as numbered troubleshooting steps.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 72 rules
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/agents (sha: 3af27dde) — View relationship
Review mode: 🧠 Deep: This security-sensitive behavior change spans GitHub/GitLab/Jira identity resolution, comment matching, retries, dry-run semantics, and multiple client/test paths, creating several independent opportunities for subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Findings visible per group, which tucks the rest behind a View link

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/guides/user/issues-commands.md Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:52 PM UTC · Completed 3:30 PM UTC

Commit: d85da5a · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $3.66

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Site preview

Preview: https://5625e0f8-site.fullsend-ai.workers.dev

Commit: be3093ce5a33a74f20a29c05f610148525b4dc8f

@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.14286% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/cli/issues.go 96.22% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Oct 1, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Moderate file and line churn with no protected paths or CI changes, balanced by an established author, zero history of reverts, and strong issue acceptance criteria alignment despite modifying existing CLI behavior without a feature flag; anchored to the prior review's score of 2 (moderate) since Tier 1 signals (file/line counts, protected paths, CI/dependency surface) are unchanged — only test and doc wording changed since the prior assessment.

Previous run

Risk Assessment: moderate (2/5)

Details

Moderate file and line churn with no protected paths or CI changes, balanced by an established author, zero history of reverts, and strong issue acceptance criteria alignment despite modifying existing CLI behavior without a feature flag.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review

Findings

High

  • [sub-agent-failure] N/A — The correctness sub-agent did not return findings: it was dispatched at full scope and timed out after 900s (the same failure mode observed in the prior review of this PR). Correctness is an Opus-tier, safety-critical review dimension for this PR (identity-resolution retry/backoff logic, fail-closed error handling, and author-matching across the GitHub/GitLab and Jira code paths) and was not independently evaluated by this review run.
  • [sub-agent-failure] N/A — The security sub-agent did not return findings: it was dispatched at full scope and timed out after 900s (the same failure mode observed in the prior review of this PR). This PR's core purpose is closing a comment-author-spoofing gap by moving from fail-open marker-only matching to fail-closed identity verification — exactly the security dimension's area. The fail-closed guarantee (no path where an empty/unresolved identity matches a comment, no silent degrade-to-unfiltered-search fallback, --dry-run still requiring identity resolution) was not independently verified by this review run.

Low

  • [sub-agent-failure] N/A — The challenger sub-agent did not return findings: it timed out after 900s. Using the pre-challenger finding set (the two high findings above) unadjudicated.

Other dimensions (clean)

  • style-conventions: No findings. Both prior-review low findings (TestForgeClientAuthenticatedUser naming, TestJiraClientAuthenticatedUser placement) are no longer observed in the current diff — the tests were renamed/moved as recommended.
  • docs-currency: No findings. docs/guides/user/issues-commands.md was checked against the identity-verification, fail-closed/retry, and --dry-run behavior introduced in this PR and found to accurately reflect it; no other documentation file was found to reference the changed identifiers.
  • risk-assessment: moderate (2/5), anchored to the prior run's score — no material change in file/line footprint, protected paths, or CI/dependency surface since the prior assessment.

This is a re-review. Since the prior review (SHA d85da5a4018d7e65e2e506b7a6e3f056bd2d4271), only three files changed: docs/guides/user/issues-commands.md (wording clarifications to the already-updated Trust model and recovery sections), and two test files (internal/tracker/jira_client_test.go, internal/tracker/tracker_test.go) that renamed/moved tests to address the prior style findings. The production logic in internal/cli/issues.go, internal/tracker/forge_client.go, internal/tracker/jira_client.go, and internal/tracker/fake_jira.go is unchanged since the prior review, but because the prior review's correctness and security sub-agents also failed to complete against that same code, it has still not received an independent correctness or security assessment. This PR's entire purpose is a security-sensitive, fail-closed identity-verification change, so these gaps should be resolved with a clean run of this review (or manual review) before merge.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

High

  • [sub-agent-failure] N/A — The correctness sub-agent did not return findings: it was dispatched twice (full scope, then with an explicit efficiency/time-budget constraint) and timed out after 900s both times. Correctness is an Opus-tier, safety-critical review dimension for this PR (identity-resolution retry/backoff logic, fail-closed error handling, and author-matching across the GitHub/GitLab and Jira code paths) and was not actually evaluated by this review run.
  • [sub-agent-failure] N/A — The security sub-agent did not return findings: it was dispatched twice (full scope, then with an explicit efficiency/time-budget constraint) and timed out after 900s both times. This PR's core purpose is closing a comment-spoofing (fail-open → fail-closed) gap, which is exactly the security dimension's area — the fail-closed guarantee (no path where an empty/unresolved identity matches a comment, no silent degrade-to-unfiltered-search fallback, --dry-run still requiring identity resolution) was not independently verified by this review run.

Low

  • [naming-convention] internal/tracker/tracker_test.go:400 — TestForgeClientAuthenticatedUser deviates from the naming convention established elsewhere in this file, where receiver-method tests follow Test<Receiver>_<Method> (e.g. TestForgeClient_GetIssue, TestForgeClient_ListComments, TestForgeClient_CreateComment).
    Remediation: Rename to TestForgeClient_AuthenticatedUser.
  • [code-organization] internal/tracker/tracker_test.go:414 — TestJiraClientAuthenticatedUser is placed in tracker_test.go rather than jira_client_test.go (where other JiraClient unit tests live, e.g. TestJiraClient_GetIssue, TestJiraClient_ListComments), and its name omits the receiver-method underscore separator used elsewhere. This also pulls an internal/forge/jira import into tracker_test.go that would otherwise be unnecessary there.
    Remediation: Move the test to internal/tracker/jira_client_test.go, rename it to TestJiraClient_AuthenticatedUser, and drop the now-unneeded jira import from tracker_test.go.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@ggallen ggallen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the changes and found no actionable issues.

…ery steps

TestForgeClient_AuthenticatedUser and TestJiraClient_AuthenticatedUser
follow the Test<Receiver>_<Method> convention, and the Jira one moves to
jira_client_test.go with the other JiraClient tests. The user guide's
identity-failure guidance becomes numbered steps.

Assisted-by: Claude (fix), Claude (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:46 PM UTC · Completed 4:25 PM UTC

Commit: be3093c · View workflow run →

Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $2.87

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

@waynesun09
waynesun09 dismissed stale reviews from fullsend-ai-review[bot] and fullsend-ai-review[bot] October 1, 2026 16:38

No substantive findings: this round's bot review consists only of sub-agent timeouts (900s); 0 unresolved threads, CI green, ggallen approved.

@waynesun09
waynesun09 added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 965b721 Oct 1, 2026
62 checks passed
@waynesun09
waynesun09 deleted the issues-post-comment-author-check branch October 1, 2026 17:13
jctanner pushed a commit to jctanner/fullsend that referenced this pull request Oct 1, 2026
…y is unresolved

sticky.Post resolved the poster's login with GetAuthenticatedUser and,
on failure, logged a warning and continued with an empty login.
FindMarkedComment skipped its author filter whenever botUser was
empty, so it matched the first comment carrying the marker regardless
of who wrote it, letting an unverified (and potentially spoofed)
comment get edited in-place. fullsend-ai#7243/fullsend-ai#7916 already closed this gap for
postTrackerStickyComment; this brings sticky.Post (used by
post-comment / post-review) to the same rule.

Changes in internal/sticky/sticky.go:
* Added resolveBotUser, which retries a failed or empty
  GetAuthenticatedUser lookup per a package-level
  botUserLookupBackoff (tests shorten it), honouring ctx
  cancellation. This mirrors resolveTrackerSelf in
  internal/cli/issues.go, but is kept local to this package since
  internal/cli imports sticky and sharing it would create an import
  cycle.
* Post now calls resolveBotUser before listing or writing anything.
  If the login can't be resolved, it returns an error naming the
  cause (wrapping the lookup error) and calls neither
  UpdateIssueComment nor CreateIssueComment, including on dry run,
  so dry-run output never implies an edit would occur when none
  safely could.
* FindMarkedComment now treats an empty botUser as "match nothing"
  instead of "match any author," and requires an exact author match
  plus the marker.

Tests in internal/sticky/sticky_test.go:
* Inverted TestFindMarkedComment_EmptyBotUser to assert nil instead
  of matching any author.
* Added coverage for a persistent identity-lookup failure (errors,
  posts/edits nothing), a transient failure that succeeds on retry
  (edits the bot's own comment), a planted marker comment alongside
  the bot's own comment (only the bot's comment is edited), a lone
  planted comment (a new comment is created and the planted one is
  untouched), and dry run with an unresolved identity (errors,
  doesn't claim an edit would happen).
* flakyAuthClient wraps forge.Client to fail GetAuthenticatedUser a
  configurable number of times before succeeding, since FakeClient's
  Errors map isn't a call-sequence.
* Set AuthenticatedUser on the fake client in TestPost_DryRun and
  TestPost_DryRunExisting, which previously relied on the degraded
  fallback (an empty login) succeeding.

Scope: internal/cli/issues.go (and its resolveTrackerSelf /
postTrackerStickyComment) is untouched; that package already has its
own equivalent, added by fullsend-ai#7916, and the issue asks to keep the retry
local to internal/sticky.

Closes fullsend-ai#7884

This branch was successfully deployed

1 active and 1 inactive deployments
site-preview — be3093ce Deployed Oct 1, 2026 by github-actions[bot]
dev — be3093ce Deployed Oct 1, 2026 by waynesun09 via behaviour #14828
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/moderate PR risk: moderate

Projects

None yet

2 participants