Skip to content

GH#4150, GH#3149, GH#4155: fix test harness, security posture output, and GraphQL injection#4165

Merged
marcusquinn merged 3 commits intomarcusquinn:mainfrom
johnwaldo:bugfix/quality-debt-fixes
Mar 11, 2026
Merged

GH#4150, GH#3149, GH#4155: fix test harness, security posture output, and GraphQL injection#4165
marcusquinn merged 3 commits intomarcusquinn:mainfrom
johnwaldo:bugfix/quality-debt-fixes

Conversation

@johnwaldo
Copy link
Contributor

@johnwaldo johnwaldo commented Mar 11, 2026

Summary

  • Fix assert_line_exists() returning 1 on failure, causing early exit under set -e — now returns 0 consistent with assert_contains()
  • Remove 2>/dev/null from security-posture-helper.sh invocation in aidevops init so users see detailed security findings
  • Prevent GraphQL injection in stats-functions.sh via parameterized queries and slug validation

Details

Test harness (GH#4150): assert_line_exists() returned 1 on failure while assert_contains() returned 0. Both use fail() which returns 0 — the intent is to collect all failures and report at the end. Fixed to return 0 matching assert_contains().

Security posture (GH#3149): bash "$security_posture_script" store "$project_root" 2>/dev/null suppressed all stderr, hiding detailed security findings during aidevops init. The exit code is already checked — removing 2>/dev/null lets the detailed report through.

GraphQL injection (GH#4155, 5 findings):

  • Added _validate_repo_slug() — rejects path traversal, quotes, and injection characters before API use
  • Parameterized _cleanup_stale_pinned_issues() GraphQL query using -F owner/-F name variables instead of string interpolation
  • Parameterized quality-debt search queries using -F searchQuery variable
  • Added slug validation gate in _get_runner_role() before API path construction
  • Redirected GraphQL stderr to $LOGFILE instead of /dev/null for debuggability
  • Updated auto-generated message references from pulse-wrapper.sh to stats-wrapper.sh (t1431 extraction)

Closes #4150
Closes #3149
Closes #4155

Summary by CodeRabbit

  • Tests

    • Improved assertion behavior to record failures while preserving overall test flow (failures are reported without forcing immediate exit).
  • Chores

    • Exposed error output from the security posture invocation for clearer troubleshooting.
    • Hardened repo/stat operations: input validation, safer parameterized API calls, sanitized outputs, redirected logs, and updated user-facing status text from “pulse” to “stats.”

- assert_line_exists(): return 0 on failure (consistent with assert_contains)
  so the test harness runs all checks before reporting, instead of exiting
  on the first failure under set -e. The set +e wrapper in run_checks()
  mitigated this, but the function contract was still wrong.
  Closes marcusquinn#4150

- aidevops.sh: remove 2>/dev/null from security-posture-helper.sh invocation
  so users see detailed security findings during 'aidevops init' instead of
  only the pass/fail summary.
  Closes marcusquinn#3149
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the robustness of the test harness by standardizing error handling in assertion functions and improves the user experience during aidevops init by making security assessment details visible. These changes collectively contribute to more reliable testing and more transparent security feedback for developers.

Highlights

  • Test Harness Reliability: Corrected assert_line_exists() to return 0 on failure, aligning its behavior with assert_contains() and preventing premature script exits under set -e.
  • Security Posture Visibility: Removed 2>/dev/null from the security-posture-helper.sh invocation in aidevops init to allow detailed security findings to be displayed to the user.
Changelog
  • .agents/scripts/tests/test-pr-3885-recovery.sh
    • Modified assert_line_exists() to return 0 instead of 1 on failure, ensuring consistent error handling within the test suite.
  • aidevops.sh
    • Removed the 2>/dev/null redirection from the security-posture-helper.sh command, allowing standard error output from the security assessment to be displayed to the user.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces two beneficial changes. First, it corrects the return code in the assert_line_exists test helper to ensure consistent behavior and prevent premature test suite termination. Second, it removes stderr suppression from the security posture assessment script, which will now correctly display detailed findings to the user. Both changes are well-reasoned and improve the tool's robustness and user experience. The implementation is correct, and I have no further suggestions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Test helpers now record failures without causing early exits; init reveals security-posture stderr; stats scripts gain repo-slug validation, parameterized GraphQL calls, redirected logs, and wording updates from "pulse" to "stats".

Changes

Cohort / File(s) Summary
Test Harness
.agents/scripts/tests/test-pr-3885-recovery.sh
Modified assertion helpers: added assert_line_exists; failing assertions call fail() and return 0 to avoid premature exit under set -e; tightened exact-line .gitignore checks.
Init / Security Helper
aidevops.sh
Removed 2>/dev/null from security-posture-helper invocation so stderr is no longer suppressed during aidevops init.
Stats / GitHub integrations
.agents/scripts/stats-functions.sh
Added _validate_repo_slug(), added defensive validation/logging, switched inline GraphQL interpolation to parameterized gh api usage, redirected cli errors to LOGFILE, and updated text references from “pulse” to “stats”.

Sequence Diagram(s)

sequenceDiagram
    participant Script as Script (.agents/scripts/stats-functions.sh)
    participant GH as gh CLI
    participant GitHub as GitHub GraphQL API
    participant Log as LOGFILE

    rect rgba(0,128,255,0.5)
    Script->>GH: call `gh api` with -F owner/-F name (parameterized query)
    end
    GH->>GitHub: send GraphQL request
    GitHub-->>GH: return JSON response
    GH->>Log: redirect stdout/stderr to LOGFILE
    Script->>Script: call `_validate_repo_slug()` and sanitize fields
    Script->>Log: write status / update health issue body (stats context)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #4133 — Introduced the test harness additions that this change adjusts (same test file).
  • #3093 — Earlier discussion that originally suppressed security-posture-helper stderr; this change restores visible stderr.
  • #4110 — Prior PR modifying stats-functions; related to GraphQL, slug handling, and wording updated here.

Suggested labels

bug, needs-maintainer-review

Poem

🛠️ Tests keep counting, no abrupt goodbye,

Warnings now spoken, not whispered awry.
GraphQL calls tightened, logs sing the stats,
Init shows its truth, and the harness stays pat. 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the three main changes: test harness fix, security posture output exposure, and GraphQL injection prevention with specific issue numbers.
Linked Issues check ✅ Passed All code changes directly address the three linked issues: #4150 (assert_line_exists return 0), #3149 (stderr suppression removed), #4155 (GraphQL parameterization and slug validation).
Out of Scope Changes check ✅ Passed All changes are scoped to the three linked issues. Minor message text updates referencing stats-wrapper.sh are directly related to #4155 scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…ions.sh

- Add _validate_repo_slug() to reject path traversal and injection
  characters in repo slugs before they reach API paths
- Parameterize GraphQL queries in _cleanup_stale_pinned_issues() using
  -F owner/name variables instead of string interpolation (prevents
  injection via double quotes in repo slug)
- Parameterize GraphQL search queries for quality-debt counts using
  -F searchQuery variable instead of inline interpolation
- Add slug validation gate in _get_runner_role() before API path
  construction
- Redirect GraphQL stderr to LOGFILE instead of /dev/null for
  debuggability
- Update auto-generated message references from pulse-wrapper.sh to
  stats-wrapper.sh to reflect the t1431 extraction

Closes marcusquinn#4155
@johnwaldo johnwaldo changed the title GH#4150, GH#3149: fix test harness early exit and security posture stderr suppression GH#4150, GH#3149, GH#4155: fix test harness, security posture output, and GraphQL injection Mar 11, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.agents/scripts/stats-functions.sh (1)

657-662: Consider parameterizing the remaining GraphQL mutations for consistency.

While the read queries are now parameterized, the mutation at lines 657-662 still uses string interpolation for node_id:

gh api graphql -f query="
    mutation {
        unpinIssue(input: {issueId: \"${node_id}\"}) {

The node_id originates from GitHub's API response (line 651), so it's from a trusted source. However, for consistency with the new parameterized pattern and defense-in-depth, this could be refactored:

♻️ Optional: Parameterize the mutation
-		gh api graphql -f query="
-			mutation {
-				unpinIssue(input: {issueId: \"${node_id}\"}) {
-					issue { number }
-				}
-			}" >/dev/null 2>&1 || true
+		gh api graphql -F nodeId="$node_id" -f query='
+			mutation($nodeId: ID!) {
+				unpinIssue(input: {issueId: $nodeId}) {
+					issue { number }
+				}
+			}' >/dev/null 2>&1 || true

Similar patterns exist at lines 301-306, 321-326, 685-690, and 987-992. This is a good-to-have refinement for a follow-up, not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/stats-functions.sh around lines 657 - 662, The GraphQL
mutation call using gh api graphql that invokes unpinIssue with inline string
interpolation of "${node_id}" should be converted to a parameterized query like
the other read queries: pass a separate variable (e.g., issueId) via -f
variables='{"issueId":"'"$node_id"'"}' and update the GraphQL payload to use the
$issueId variable in the mutation, modifying the gh api graphql invocation that
currently contains unpinIssue(input: {issueId: \"${node_id}\"}) to accept and
reference $issueId; apply the same parameterization pattern to the other similar
mutation calls (the other gh api graphql invocations that inline node_id) for
consistency and defense-in-depth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.agents/scripts/stats-functions.sh:
- Around line 657-662: The GraphQL mutation call using gh api graphql that
invokes unpinIssue with inline string interpolation of "${node_id}" should be
converted to a parameterized query like the other read queries: pass a separate
variable (e.g., issueId) via -f variables='{"issueId":"'"$node_id"'"}' and
update the GraphQL payload to use the $issueId variable in the mutation,
modifying the gh api graphql invocation that currently contains
unpinIssue(input: {issueId: \"${node_id}\"}) to accept and reference $issueId;
apply the same parameterization pattern to the other similar mutation calls (the
other gh api graphql invocations that inline node_id) for consistency and
defense-in-depth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ec01e302-3714-4d3f-9e1a-e33f73961f37

📥 Commits

Reviewing files that changed from the base of the PR and between 7d4f44c and 18664ca.

📒 Files selected for processing (1)
  • .agents/scripts/stats-functions.sh

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
.agents/scripts/stats-functions.sh (2)

114-118: Good fail-closed pattern for slug validation.

Returning "contributor" on invalid slugs correctly denies elevated permissions. However, the calling function _update_health_issue_for_repo() continues executing with the same repo_slug in subsequent gh commands (lines 225, 238, 264, etc.) without knowing validation failed here.

Since slugs originate from repos.json (trusted configuration), the practical risk is minimal — invalid slugs would just produce harmless API 404s. For stronger defense-in-depth, consider validating at the entry point of _update_health_issue_for_repo() and returning early on failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/stats-functions.sh around lines 114 - 118, The
_validate_repo_slug() check currently returns "contributor" on invalid slugs but
allows callers like _update_health_issue_for_repo() to continue using the same
repo_slug; add an early validation at the start of
_update_health_issue_for_repo() that calls _validate_repo_slug "$repo_slug" and
returns immediately (or exits with the same safe fallback) if validation fails
so subsequent gh API calls are not attempted with an invalid slug; reference the
existing _validate_repo_slug and the repo_slug parameter in your change.

686-691: Consider parameterizing remaining GraphQL mutations for consistency.

Several GraphQL mutations still use string interpolation for issueId (lines 301-306, 321-326, 658-663, 686-691, 988-993). While these node_id values originate from GitHub API responses rather than user input, parameterizing them would complete the security hardening effort and provide defense-in-depth.

Example refactor for the mutation at line 686:

♻️ Optional: Parameterize unpinIssue mutation
-	gh api graphql -f query="
-		mutation {
-			unpinIssue(input: {issueId: \"${issue_node_id}\"}) {
-				issue { number }
-			}
-		}" >/dev/null 2>&1 || true
+	gh api graphql -F nodeId="$issue_node_id" -f query='
+		mutation($nodeId: ID!) {
+			unpinIssue(input: {issueId: $nodeId}) {
+				issue { number }
+			}
+		}' >/dev/null 2>&1 || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/stats-functions.sh around lines 686 - 691, The gh api
graphql call that embeds "${issue_node_id}" directly in the mutation (the
unpinIssue mutation using the gh api graphql command) should be changed to a
parameterized GraphQL query: move the issueId into a GraphQL variable (e.g.,
$issueId) in the mutation string and pass the value via gh's -f flag (e.g., -f
issueId="$issue_node_id") instead of interpolating the variable into the query;
update the mutation string used in the unpinIssue call and apply the same
pattern to the other occurrences that interpolate node IDs to complete the
hardening.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.agents/scripts/stats-functions.sh:
- Around line 114-118: The _validate_repo_slug() check currently returns
"contributor" on invalid slugs but allows callers like
_update_health_issue_for_repo() to continue using the same repo_slug; add an
early validation at the start of _update_health_issue_for_repo() that calls
_validate_repo_slug "$repo_slug" and returns immediately (or exits with the same
safe fallback) if validation fails so subsequent gh API calls are not attempted
with an invalid slug; reference the existing _validate_repo_slug and the
repo_slug parameter in your change.
- Around line 686-691: The gh api graphql call that embeds "${issue_node_id}"
directly in the mutation (the unpinIssue mutation using the gh api graphql
command) should be changed to a parameterized GraphQL query: move the issueId
into a GraphQL variable (e.g., $issueId) in the mutation string and pass the
value via gh's -f flag (e.g., -f issueId="$issue_node_id") instead of
interpolating the variable into the query; update the mutation string used in
the unpinIssue call and apply the same pattern to the other occurrences that
interpolate node IDs to complete the hardening.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f7ba11d-0a84-4ed1-8b9f-f1450c74678e

📥 Commits

Reviewing files that changed from the base of the PR and between 18664ca and b38bcd1.

📒 Files selected for processing (1)
  • .agents/scripts/stats-functions.sh

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

Labels

None yet

Projects

None yet

2 participants