Skip to content

feat: PR security checks — suspicious paths, committer identity, auto-merge override#1109

Merged
myakove merged 15 commits into
mainfrom
feat/issue-1106-pr-security-checks
Jun 10, 2026
Merged

feat: PR security checks — suspicious paths, committer identity, auto-merge override#1109
myakove merged 15 commits into
mainfrom
feat/issue-1106-pr-security-checks

Conversation

@myakove

@myakove myakove commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

PR Summary by Qodo

Add PR security checks for suspicious paths, committer identity, and auto-merge override
✨ Enhancement 🧪 Tests ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

Walkthroughs

User Description

Summary

Three configurable security checks to detect and block malicious PR attack vectors:

Suspicious Path Detection

  • New security-suspicious-paths check run
  • Configurable path prefixes (default: .claude/, .vscode/, .cursor/, .devcontainer/, .pi/, .github/workflows/, .github/actions/)
  • Fails check when PR modifies files in sensitive locations

Committer Identity Check

  • New security-committer-identity check run
  • Flags when last committer differs from PR author
  • Handles unknown committer identity explicitly

Auto-Merge Override

  • Blocks auto-merge when PR touches suspicious paths
  • Posts comment explaining why auto-merge was blocked

Security Design

  • Config only from server-side config.yaml (not overridable by in-repo .github-webhook-server.yaml)
  • Prevents attackers from weakening security policy via PR

Closes #1106

AI Description
• Add configurable PR security check runs for suspicious paths and committer identity.
• Block auto-merge and comment when PR touches security-sensitive path prefixes.
• Document/validate security-checks config and add full test coverage for new behavior.
Diagram
graph TD
  cfg["server config.yaml"] --> wh(["GitHubWebhook"]) --> prh(["PullRequestHandler"]) --> ofh(["OwnersFileHandler"])
  prh --> rh(["RunnerHandler"]) --> crh(["CheckRunHandler"]) --> gh{{"GitHub API"}}
  prh -. "comment / enable automerge" .-> gh

  subgraph Legend
    direction LR
    _file["Config/File"] ~~~ _svc(["Handler/Service"]) ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use CODEOWNERS + branch protection for sensitive paths
  • ➕ Native GitHub enforcement; no extra webhook-side logic
  • ➕ Can require reviews for .github/workflows/ and similar paths
  • ➖ CODEOWNERS lives in-repo; attacker PR may attempt to modify it (mitigations require additional protections)
  • ➖ Doesn’t detect committer mismatch vs PR author
  • ➖ Doesn’t provide an explicit auto-merge override message path
2. Required GitHub Actions workflow with path filters
  • ➕ Clear CI signal in GitHub UI; can block merges via required checks
  • ➕ Easy to extend with additional detectors
  • ➖ Workflows are in-repo and are themselves part of the attack surface
  • ➖ May not run as expected depending on repo settings/permissions; weaker central enforcement than server-side config
3. Org policy: require verified commits / signature enforcement
  • ➕ Stronger identity assurance than comparing GitHub usernames alone
  • ➕ Moves trust decision into cryptographic verification
  • ➖ Org-wide operational overhead; may block legitimate contributors
  • ➖ Doesn’t address suspicious-path modifications; still needs path-based review controls

Recommendation: The PR’s approach (server-side, non-overridable security policy + explicit check-runs + auto-merge override) is a good fit for defending against repo-config supply-chain attacks. Consider adding CODEOWNERS/branch protections as defense-in-depth, but keep these webhook checks as the centrally enforced gate.

Grey Divider

File Changes

Enhancement (4)
github_api.py Load server-side security policy and make committer identity explicit +10/-1

Load server-side security policy and make committer identity explicit

• Sets last_committer to "unknown" when no GitHub user is associated with the last commit committer. Loads security-checks only from server configuration (no per-repo extra_dict override) and exposes security_suspicious_paths and security_committer_identity_check on the webhook context.

webhook_server/libs/github_api.py


pull_request_handler.py Queue/run security check-runs and block auto-merge on suspicious paths +63/-0

Queue/run security check-runs and block auto-merge on suspicious paths

• Adds a Security Checks section to the welcome comment, queues and launches two new security check-runs during PR open/sync, and blocks auto-merge with an explanatory PR comment when changed_files match suspicious path prefixes.

webhook_server/libs/handlers/pull_request_handler.py


runner_handler.py Implement suspicious-path and committer-identity security check runners +114/-0

Implement suspicious-path and committer-identity security check runners

• Adds runner methods to evaluate changed files against configured prefixes and to compare PR author vs last committer (including an explicit "unknown" case). Each runner publishes detailed check-run outputs and sets success/failure accordingly.

webhook_server/libs/handlers/runner_handler.py


constants.py Define security check names and default suspicious path prefixes +14/-0

Define security check names and default suspicious path prefixes

• Introduces constants for the two new check-run names, registers them as non-overridable built-in checks, and defines the default suspicious path prefix list used for detection.

webhook_server/utils/constants.py


Tests (2)
test_pull_request_handler.py Extend PR handler tests to account for new security runners +9/-0

Extend PR handler tests to account for new security runners

• Updates the webhook mock with security-related attributes and patches the new runner methods in existing workflow tests. Ensures PR processing test scaffolding stays compatible with the new queued/started tasks.

webhook_server/tests/test_pull_request_handler.py


test_security_checks.py Add dedicated tests for security checks and auto-merge override +459/-0

Add dedicated tests for security checks and auto-merge override

• Adds coverage for suspicious path detection outcomes, committer identity match/mismatch/unknown, and auto-merge blocking behavior (including comment posting). Also asserts security constants and default suspicious path list are wired into BUILTIN_CHECK_NAMES.

webhook_server/tests/test_security_checks.py


Other (2)
config.yaml Document security-checks settings and defaults in example config +14/-0

Document security-checks settings and defaults in example config

• Adds a new security-checks section with default suspicious path prefixes and a committer identity toggle. Provides inline commentary describing the intent of these checks.

examples/config.yaml


schema.yaml Add JSON schema for security-checks configuration +24/-0

Add JSON schema for security-checks configuration

• Introduces a security-checks schema definition (suspicious-paths list and committer-identity-check boolean) and wires it into the root schema. Documents behavioral effects (check-run failure and auto-merge blocking).

webhook_server/config/schema.yaml


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (1) 📎 Requirement gaps (2)

Context used

Grey Divider


Action required

1. security-checks not merged 📎 Requirement gap ≡ Correctness
Description
_repo_data_from_config() reads security-checks from only the highest-precedence config scope and
does not merge/extend suspicious-paths across global, per-repo, and in-repo levels. This violates
the required override/extension behavior and can prevent repos from extending defaults without fully
replacing them.
Code

webhook_server/libs/github_api.py[R957-966]

+        _security_checks: dict[str, Any] | None = self.config.get_value(
+            value="security-checks", return_on_none=None, extra_dict=repository_config
+        )
+        _security_config = _security_checks if isinstance(_security_checks, dict) else {}
+        _suspicious_paths = _security_config.get("suspicious-paths", DEFAULT_SUSPICIOUS_PATHS)
+        self.security_suspicious_paths: list[str] = (
+            [str(p).strip() for p in _suspicious_paths if isinstance(p, (str, int, float)) and str(p).strip()]
+            if isinstance(_suspicious_paths, list)
+            else DEFAULT_SUSPICIOUS_PATHS
+        )
Evidence
The checklist requires merged effective settings across config levels, including extension/override
semantics for suspicious-paths. The added code calls `Config.get_value('security-checks',
extra_dict=repository_config)` which returns the first matching dict by precedence (no merge), so
only one scope’s suspicious-paths can ever be applied.

Repository configuration loader reads and merges security-checks across all config levels
webhook_server/libs/github_api.py[957-966]
webhook_server/libs/config.py[132-153]

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

## Issue description
`security-checks` is read as a single dict from the first config scope that defines it, so `suspicious-paths` cannot be extended/combined across global/per-repo/in-repo config levels as required.

## Issue Context
`Config.get_value()` returns the first non-`None` value (no merging). `_repo_data_from_config()` therefore cannot compute an “effective” `security-checks` config that supports extension.

## Fix Focus Areas
- webhook_server/libs/github_api.py[957-966]
- webhook_server/libs/config.py[132-153]

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


2. committer-identity-check type unchecked ✓ Resolved 📎 Requirement gap ☼ Reliability
Description
GithubWebhook._repo_data_from_config() assigns security_committer_identity_check directly from
raw YAML config without validating it is a boolean, so non-boolean values (e.g., the quoted string
"false") can be treated as truthy and unintentionally keep the committer-identity check enabled and
affect PR processing/required checks. This violates the requirement that committer-identity-check
defaults to true but can be reliably disabled via config.
Code

webhook_server/libs/github_api.py[967]

+        self.security_committer_identity_check: bool = _security_config.get("committer-identity-check", True)
Evidence
The requirement states committer-identity-check must default to true yet be disable-able via
configuration, but the cited code sets self.security_committer_identity_check straight from
_security_config.get(...) and config values are returned as-is without coercion or schema
validation. Because this field is later evaluated in boolean contexts to decide whether to run the
check and (when security_mandatory is true) to include security-committer-identity as a required
status check, any non-bool but truthy value (such as a quoted string) will unexpectedly enable
enforcement and defeat intended disablement.

Committer identity check run fails when last committer differs from PR author/parent committer
webhook_server/libs/github_api.py[967-967]
webhook_server/libs/github_api.py[957-975]
webhook_server/libs/config.py[132-153]
webhook_server/libs/handlers/runner_handler.py[370-378]
webhook_server/libs/handlers/check_run_handler.py[432-438]

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

## Issue description
`security-checks.committer-identity-check` is read from YAML and stored in `self.security_committer_identity_check` without runtime type validation, so malformed non-boolean values (e.g., a quoted string like `"false"`) can be truthy and unintentionally enable the committer identity check and its required-gating behavior.

## Issue Context
- The checklist/requirement expects `committer-identity-check` to default to `true` but be reliably disable-able via config.
- Config loading returns raw YAML values (no type coercion/validation), yet downstream logic relies on boolean truthiness of `security_committer_identity_check` to determine whether to run the check and whether it becomes required when mandatory security checks are enabled.
- Fix should mirror the validation pattern used elsewhere (e.g., for `security-checks.mandatory`): read the raw value, validate `isinstance(..., bool)`, warn on invalid types, and fall back to the intended default.

## Fix Focus Areas
- webhook_server/libs/github_api.py[957-975]

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


3. Example suspicious paths incomplete 📎 Requirement gap ⚙ Maintainability
Description
The in-repo example config lists only two security-checks.suspicious-paths entries, which does not
match the documented default blocked list. This can mislead users about what the default security
policy protects and how to configure it correctly.
Code

examples/.github-webhook-server.yaml[R175-181]

+# Security Checks (overrides global config)
+security-checks:
+  mandatory: true
+  suspicious-paths:
+    - ".github/workflows/"
+    - ".github/actions/"
+  committer-identity-check: true
Evidence
PR Compliance ID 9 requires examples/.github-webhook-server.yaml to document security-checks
usage and to match the documented defaults. The added example lists only .github/workflows/ and
.github/actions/, omitting other documented default prefixes like .claude/, .vscode/,
.cursor/, .devcontainer/, and .pi/.

Update example configuration to document security-checks usage
examples/.github-webhook-server.yaml[175-181]

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

## Issue description
`examples/.github-webhook-server.yaml` documents `security-checks.suspicious-paths` with only two prefixes, but the compliance requirement expects the example to match the documented defaults.

## Issue Context
Compliance requires the example configuration to demonstrate `security-checks` usage and reflect the documented default suspicious-path prefixes.

## Fix Focus Areas
- examples/.github-webhook-server.yaml[175-181]

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


View more (10)
4. Security checks stuck in-progress ✓ Resolved 🐞 Bug ☼ Reliability
Description
RunnerHandler.run_security_suspicious_paths() and run_security_committer_identity() set check
runs to in_progress but don’t catch unexpected exceptions, so a runtime error can prevent a
terminal conclusion from being posted and (when mandatory) block merges indefinitely. This is
inconsistent with run_check(), which converts unexpected exceptions into a failure check run.
Code

webhook_server/libs/handlers/runner_handler.py[R316-397]

+    async def run_security_suspicious_paths(self) -> None:
+        """Check if PR modifies security-sensitive paths.
+
+        Fails the check run if any changed files match the configured suspicious path prefixes.
+        Uses changed_files from owners_file_handler (already computed during PR processing).
+        """
+        suspicious_paths = self.github_webhook.security_suspicious_paths
+        if not suspicious_paths:
+            self.logger.debug(f"{self.log_prefix} No suspicious paths configured, skipping security check")
+            return
+
+        await self.check_run_handler.set_check_in_progress(name=SECURITY_SUSPICIOUS_PATHS_STR)
+
+        changed_files = self.owners_file_handler.changed_files
+        matched_files = [f for f in changed_files if any(f.startswith(prefix) for prefix in suspicious_paths)]
+
+        if matched_files:
+            files_list = "\n".join(f"- `{f}`" for f in matched_files)
+            output: CheckRunOutput = {
+                "title": "\u274c Security: Suspicious Paths Detected",
+                "summary": f"{len(matched_files)} file(s) modify security-sensitive paths",
+                "text": (
+                    f"## Suspicious Path Detection\n\n"
+                    f"This PR modifies files in security-sensitive locations:\n\n"
+                    f"{files_list}\n\n"
+                    f"**Configured suspicious path prefixes:**\n"
+                    + "\n".join(f"- `{p}`" for p in suspicious_paths)
+                    + "\n\n"
+                    "These paths control development tooling, CI/CD workflows, or IDE configurations "
+                    "and require careful review to prevent supply-chain attacks."
+                ),
+            }
+            self.logger.warning(f"{self.log_prefix} PR modifies suspicious paths: {matched_files}")
+            await self.check_run_handler.set_check_failure(name=SECURITY_SUSPICIOUS_PATHS_STR, output=output)
+        else:
+            output = {
+                "title": "Security: Suspicious Paths",
+                "summary": "No security-sensitive paths modified",
+                "text": (
+                    "No changed files match the configured suspicious path prefixes.\n\n"
+                    "**Configured prefixes:**\n" + "\n".join(f"- `{p}`" for p in suspicious_paths)
+                ),
+            }
+            await self.check_run_handler.set_check_success(name=SECURITY_SUSPICIOUS_PATHS_STR, output=output)
+
+    async def run_security_committer_identity(self) -> None:
+        """Check if the last committer matches the PR author.
+
+        Fails the check run if the last commit's committer differs from the PR author
+        (parent_committer), which may indicate a commit was authored by someone unexpected.
+        """
+        if not self.github_webhook.security_committer_identity_check:
+            self.logger.debug(f"{self.log_prefix} Committer identity check disabled, skipping")
+            return
+
+        await self.check_run_handler.set_check_in_progress(name=SECURITY_COMMITTER_IDENTITY_STR)
+
+        parent_committer = self.github_webhook.parent_committer
+        last_committer = self.github_webhook.last_committer
+
+        if last_committer == "unknown":
+            output: CheckRunOutput = {
+                "title": "\u274c Security: Committer Identity Unknown",
+                "summary": "Committer identity could not be verified",
+                "text": (
+                    "## Committer Identity Check\n\n"
+                    f"**PR author:** `{parent_committer}`\n"
+                    "**Last commit committer:** unknown\n\n"
+                    "Committer identity could not be verified \u2014 "
+                    "last commit has no associated GitHub user.\n\n"
+                    "This may indicate:\n"
+                    "- A commit was made with a local Git identity not linked to a GitHub account\n"
+                    "- The committer's email is not verified on GitHub\n\n"
+                    "Please verify the commit authorship before merging."
+                ),
+            }
+            self.logger.warning(
+                f"{self.log_prefix} Committer identity unknown: "
+                f"PR author={parent_committer}, last committer has no GitHub user"
+            )
+            await self.check_run_handler.set_check_failure(name=SECURITY_COMMITTER_IDENTITY_STR, output=output)
+        elif last_committer != parent_committer:
Evidence
The new security check methods set in_progress and then proceed without any exception guard, while
the shared run_check() path explicitly catches unexpected exceptions and sets a failure
conclusion. These checks can be treated as required when security_mandatory is true, and they run
under asyncio.gather(..., return_exceptions=True), meaning an exception won’t crash the workflow
but can still leave the check run without a terminal status.

webhook_server/libs/handlers/runner_handler.py[316-427]
webhook_server/libs/handlers/runner_handler.py[210-281]
webhook_server/libs/handlers/pull_request_handler.py[1176-1208]
webhook_server/libs/handlers/check_run_handler.py[401-444]
webhook_server/libs/handlers/owners_files_handler.py[59-62]

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 security check runner methods can raise after setting their check run to `in_progress` (e.g., missing `changed_files`, unexpected types, GitHub API errors), leaving the check run without a terminal conclusion. Because these security checks can be added to the required checks list when `security_mandatory` is enabled, a stuck check can block merges indefinitely.

## Issue Context
Unlike other checks that use `RunnerHandler.run_check()` (which has explicit `CancelledError` handling and sets a failure check run on unexpected exceptions), these new security check methods run “inline” without a protective try/except.

## Fix Focus Areas
- webhook_server/libs/handlers/runner_handler.py[316-427]
- webhook_server/libs/handlers/runner_handler.py[210-281]

### Implementation notes
- Wrap each security check method body in `try/except`.
- Always re-raise `asyncio.CancelledError`.
- On any other exception, set the corresponding check run to **failure** with an output explaining the exception, then (optionally) re-raise so the error is still logged upstream.
- Consider explicitly guarding initialization (e.g., if `not hasattr(self.owners_file_handler, "changed_files")`, fail the check with a clear message).

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


5. Override bypass via stale checks 🐞 Bug ⛨ Security
Description
set_pull_request_automerge() allows auto-merge when *any* check run named
security-suspicious-paths has conclusion success, so a stale success (including one created by a
prior /security-override) can keep bypassing the suspicious-path auto-merge block even after
/security-override cancel re-runs and fails the check. This also accepts same-named successful
check runs from other apps/workflows, enabling spoofed overrides.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1268-1276]

+                # Check if security check run was already overridden (set to success by maintainer)
+                _check_runs = await github_api_call(
+                    lambda: list(self.github_webhook.last_commit.get_check_runs()),
+                    logger=self.logger,
+                    log_prefix=self.log_prefix,
+                )
+                security_check_passed = any(
+                    cr.name == SECURITY_SUSPICIOUS_PATHS_STR and cr.conclusion == SUCCESS_STR for cr in _check_runs
+                )
Evidence
The auto-merge decision uses any() over *all* check runs on the commit and does not validate
recency or origin, while /security-override and subsequent re-runs create additional check runs
(not updates). This means an older successful run (or a same-named success from another source) can
continue to satisfy the predicate and bypass the suspicious-path auto-merge block.

webhook_server/libs/handlers/pull_request_handler.py[1268-1276]
webhook_server/libs/handlers/issue_comment_handler.py[338-374]
webhook_server/libs/handlers/check_run_handler.py[159-205]

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

## Issue description
`PullRequestHandler.set_pull_request_automerge()` currently treats the suspicious-paths protection as “overridden” if **any** check run named `security-suspicious-paths` on the last commit has `conclusion == success`. Because the system creates new check runs (not updates) and because other CI sources can also emit check runs, this can:
1) Make `/security-override cancel` ineffective (older successful runs still exist and keep satisfying the `any()` predicate).
2) Allow spoofing the override by producing a same-named successful check run from another source.

## Issue Context
- The `/security-override` command sets security check runs to success.
- The cancel path re-runs checks, but does not remove older successful check runs.
- Check runs are created via `create_check_run`, which can yield multiple check runs with the same name on the same SHA.

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1268-1276]
- webhook_server/libs/handlers/issue_comment_handler.py[338-374]
- webhook_server/libs/handlers/check_run_handler.py[159-205]

## Suggested fix
- When determining whether an override is active, do **not** use `any()` across all check runs.
- Instead, filter check runs by:
 - `name == security-suspicious-paths`, **and**
 - originating app matches the webhook’s GitHub App (e.g., `cr.app.id`/`slug` matches expected), **and**
 - select the most recent run (max `id` or latest timestamp) and use only that run’s conclusion.
- Optionally, require an explicit override marker in the check run output (e.g., title includes `Overridden by maintainer`) in addition to app identity.
- Consider switching check-run updates from repeated `create_check_run` to an update-based approach (store check_run_id and update it), to avoid accumulating multiple same-named runs on a SHA.

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


6. Automerge test mock breaks ✓ Resolved 🐞 Bug ☼ Reliability
Description
test_automerge_blocked_by_suspicious_paths patches github_api_call with a bare AsyncMock, but
production code awaits it for label fetch and iterates the returned labels to build _label_names.
Under Python 3.13 this mock does not return an iterable of labels, so the test can fail or stop
exercising the real label-bypass logic.
Code

webhook_server/tests/test_security_checks.py[R382-389]

+            # Should have called github_api_call for labels fetch + blocking comment
+            comment_calls = [
+                c
+                for c in mock_api_call.call_args_list
+                if len(c.args) > 1 and isinstance(c.args[1], str) and "Auto-merge blocked" in c.args[1]
+            ]
+            assert len(comment_calls) == 1
+            assert ".github/workflows/ci.yml" in comment_calls[0].args[1]
Evidence
The production path awaits github_api_call(...) and iterates the returned value to derive label
names, while the test currently replaces github_api_call with an AsyncMock without ensuring it
returns a list-like value for the label-fetch call.

webhook_server/libs/handlers/pull_request_handler.py[1268-1274]
webhook_server/tests/test_security_checks.py[374-389]

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 test patches `github_api_call` with `AsyncMock()` but does not configure return values for calls where production expects a list of label objects. The production code iterates over the awaited result (`{label.name for label in _labels}`), which is incompatible with the current mock setup.

## Issue Context
`set_pull_request_automerge()` now fetches labels before posting the blocking comment, so the test must return an iterable for that call.

## Fix Focus Areas
- webhook_server/tests/test_security_checks.py[374-389]
- webhook_server/libs/handlers/pull_request_handler.py[1268-1274]

## Implementation notes
- In the test, patch `github_api_call` with a `side_effect` that:
 - returns `[]` for the labels-fetch lambda call(s)
 - returns `None` for `create_issue_comment` / `disable_automerge` calls
- Alternatively, patch labels fetch separately by letting the lambda execute (don’t patch `github_api_call` globally), and only patch the comment/disable calls.
- Consider tightening the assertion to match the specific call (`func == mock_pull_request.create_issue_comment`) and accept both positional and `body=` forms.

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


7. security-override allows auto-merge ✓ Resolved 📎 Requirement gap ⛨ Security
Description
When suspicious paths are detected, set_pull_request_automerge() allows auto-merge to proceed if
the security-override label is present, instead of forcing auto_merge=False and disabling any
existing auto-merge. This violates the requirement that PRs touching suspicious paths are never
auto-merged.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1268-1307]

+                # Check if security-override label is present (maintainer bypass)
+                _labels = await github_api_call(
+                    lambda: list(pull_request.labels), logger=self.logger, log_prefix=self.log_prefix
+                )
+                _label_names = {label.name for label in _labels}
+
+                if SECURITY_OVERRIDE_LABEL_STR not in _label_names:
+                    auto_merge = False
+                    files_list = ", ".join(f"`{f}`" for f in suspicious_matches)
+                    self.logger.info(
+                        f"{self.log_prefix} Auto-merge blocked: "
+                        f"PR modifies security-sensitive paths: {suspicious_matches}"
+                    )
+                    await github_api_call(
+                        pull_request.create_issue_comment,
+                        f"Auto-merge blocked: PR modifies security-sensitive paths: {files_list}",
+                        logger=self.logger,
+                        log_prefix=self.log_prefix,
+                    )
+
+                    # Disable already-enabled auto-merge on the PR
+                    if pull_request.raw_data.get("auto_merge"):
+                        try:
+                            self.logger.info(
+                                f"{self.log_prefix} Suspicious paths detected, disabling existing auto-merge"
+                            )
+                            await github_api_call(
+                                pull_request.disable_automerge, logger=self.logger, log_prefix=self.log_prefix
+                            )
+                        except asyncio.CancelledError:
+                            raise
+                        except Exception:
+                            self.logger.exception(
+                                f"{self.log_prefix} Failed to disable auto-merge for suspicious paths PR"
+                            )
+                else:
+                    self.logger.info(
+                        f"{self.log_prefix} Suspicious paths detected but {SECURITY_OVERRIDE_LABEL_STR} "
+                        f"label present, allowing auto-merge"
+                    )
Evidence
PR Compliance ID 5 requires overriding auto-merge to false (and posting an explanatory comment)
when suspicious paths are modified. The updated code explicitly permits auto-merge when
SECURITY_OVERRIDE_LABEL_STR is present, which means suspicious-path PRs can still be auto-merged.

Add auto-merge override to block auto-merge when suspicious paths are modified
webhook_server/libs/handlers/pull_request_handler.py[1268-1307]

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 auto-merge override is bypassable via the `security-override` label. Compliance requires that PRs modifying suspicious paths are never auto-merged.

## Issue Context
`set_pull_request_automerge()` detects suspicious path modifications, then checks PR labels and logs that it is "allowing auto-merge" when `security-override` is present.

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1268-1307]

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


8. Override label auth bypass ✓ Resolved 🐞 Bug ⛨ Security
Description
set_pull_request_automerge() treats the presence of the security-override label as a “maintainer
bypass” without verifying who applied the label, so anyone with label privileges can disable the
suspicious-path auto-merge block. This contradicts the maintainer-only contract enforced by the
/security-override command and weakens mandatory security enforcement.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1268-1275]

+                # Check if security-override label is present (maintainer bypass)
+                _labels = await github_api_call(
+                    lambda: list(pull_request.labels), logger=self.logger, log_prefix=self.log_prefix
+                )
+                _label_names = {label.name for label in _labels}
+
+                if SECURITY_OVERRIDE_LABEL_STR not in _label_names:
+                    auto_merge = False
Evidence
The auto-merge blocker explicitly calls the label a “maintainer bypass” but only checks whether the
label exists. Separately, the /security-override command path enforces maintainer-only usage,
showing intended authorization that is not enforced when simply reading labels.

webhook_server/libs/handlers/pull_request_handler.py[1258-1307]
webhook_server/libs/handlers/issue_comment_handler.py[327-343]
webhook_server/libs/handlers/check_run_handler.py[407-424]

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

## Issue description
`security-override` is treated as a maintainer-only bypass, but the bypass is currently granted solely based on label presence. This allows any actor with label-application permissions to bypass security protections (auto-merge blocking for suspicious paths, and required security checks elsewhere).

## Issue Context
The `/security-override` command explicitly restricts usage to maintainers, but the enforcement logic in PR processing does not validate who applied the label.

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1268-1307]
- webhook_server/libs/handlers/check_run_handler.py[407-424]
- webhook_server/libs/handlers/issue_comment_handler.py[327-343]

## Implementation notes
- When `security-override` label is present, verify provenance before honoring it (e.g., fetch PR/issue timeline events, find the most recent `labeled` event for `security-override`, and confirm the actor is in `OwnersFileHandler.get_all_repository_maintainers()`).
- If provenance cannot be verified, treat it as NOT overridden (log + optionally comment).
- Alternatively, if provenance validation is not feasible, adjust behavior/docs to reflect that the bypass is for “anyone who can apply labels,” not “maintainers only.”

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


9. Doc says security-checks server-only ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
examples/.github-webhook-server.yaml now states security-checks is not configurable
per-repository, which conflicts with the required support for configuring/overriding
security-checks at all config levels (including in-repo .github-webhook-server.yaml). This
misleads users and violates the documented configuration requirements for security checks.
Code

examples/.github-webhook-server.yaml[R4-7]

+#
+# NOTE: security-checks is NOT configurable per-repository.
+# Security policy is controlled server-side only (in config.yaml) to prevent
+# attackers from weakening security checks via in-repo config overrides.
Evidence
The new comment explicitly states security-checks is "NOT configurable per-repository" and only
controlled in config.yaml, contradicting the checklist requirements that security-checks be
supported across global/per-repo/in-repo config levels and that the in-repo example shows how to
configure it.

Add security-checks configuration schema (suspicious-paths, committer-identity-check) at all config levels
Update example configuration to include security-checks section
examples/.github-webhook-server.yaml[4-7]

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 example `.github-webhook-server.yaml` claims `security-checks` is not configurable per-repository, which contradicts the compliance requirement that `security-checks` must be definable/overridable at global, per-repo, and in-repo levels.

## Issue Context
Compliance requires `security-checks` to be supported across all config levels and for the in-repo example file to demonstrate the section; the current note tells users the opposite.

## Fix Focus Areas
- examples/.github-webhook-server.yaml[4-7]

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


10. Override label ignored ✓ Resolved 🐞 Bug ≡ Correctness
Description
set_pull_request_automerge() blocks/disables auto-merge for suspicious paths without checking the
maintainer-only security-override label, even though the welcome message and required-checks logic
treat that label as a bypass. This can unexpectedly disable an already-enabled auto-merge even after
a maintainer intentionally applied the override.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1258-1259]

+        # Also disable already-enabled auto-merge (e.g., PR gained suspicious paths after auto-merge was set)
+        if (auto_merge or pull_request.raw_data.get("auto_merge")) and self.github_webhook.security_suspicious_paths:
Evidence
The auto-merge blocker condition doesn’t consult the override label, while other parts of the system
explicitly implement /security-override as a bypass mechanism (command adds the label; required
checks skip when label exists; welcome message advertises bypass).

webhook_server/libs/handlers/pull_request_handler.py[1257-1287]
webhook_server/libs/handlers/check_run_handler.py[407-425]
webhook_server/libs/handlers/issue_comment_handler.py[327-342]
webhook_server/libs/handlers/pull_request_handler.py[641-644]
webhook_server/libs/handlers/pull_request_handler.py[766-768]

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

## Issue description
`PullRequestHandler.set_pull_request_automerge()` blocks (and can disable) auto-merge when suspicious paths are touched, but it does not respect the maintainer-only `security-override` label.

This contradicts:
- the welcome message text that advertises `/security-override` as a bypass for security checks
- the required-checks logic that explicitly skips mandatory security checks when the override label is present

## Issue Context
The new condition `(auto_merge or pull_request.raw_data.get("auto_merge"))` broadens when the suspicious-path block runs; as a result, an already-enabled auto-merge can be disabled even if maintainers applied `security-override` to proceed.

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1257-1287]
- webhook_server/libs/handlers/check_run_handler.py[407-425]
- webhook_server/libs/handlers/issue_comment_handler.py[327-342]
- webhook_server/libs/handlers/pull_request_handler.py[641-644]

## Suggested fix
1. Before applying the suspicious-path auto-merge block, fetch PR labels (same pattern as in `CheckRunHandler.all_required_status_checks`) and detect `SECURITY_OVERRIDE_LABEL_STR`.
2. If the override label is present, skip:
  - posting the “Auto-merge blocked…” comment
  - disabling existing auto-merge
  - forcing `auto_merge = False`
3. Add/extend a unit test asserting that with `security-override` label present, suspicious-path changes do not block/disable auto-merge.

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


11. disable_automerge swallows cancellation ✓ Resolved 📘 Rule violation ☼ Reliability
Description
set_pull_request_automerge() catches broad Exception around disable_automerge and does not
re-raise asyncio.CancelledError, which can break async cancellation semantics and leave tasks
stuck during shutdown/timeouts.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1279-1287]

+                if pull_request.raw_data.get("auto_merge"):
+                    try:
+                        self.logger.info(f"{self.log_prefix} Suspicious paths detected, disabling existing auto-merge")
+                        await github_api_call(
+                            pull_request.disable_automerge, logger=self.logger, log_prefix=self.log_prefix
+                        )
+                    except Exception:
+                        self.logger.exception(f"{self.log_prefix} Failed to disable auto-merge for suspicious paths PR")
+
Evidence
PR Compliance ID 19 requires re-raising asyncio.CancelledError when caught; the new `except
Exception:` block logs and continues, which would also catch and swallow cancellation exceptions.

CLAUDE.md: Use logger.exception() for Exceptions and Re-raise asyncio.CancelledError
webhook_server/libs/handlers/pull_request_handler.py[1279-1287]

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

## Issue description
`set_pull_request_automerge()` wraps the `pull_request.disable_automerge` call with `except Exception:` and logs, but does not re-raise `asyncio.CancelledError`. This can swallow cancellations in async code.

## Issue Context
Compliance requires that `asyncio.CancelledError` is re-raised when caught, and that exception handling is as specific as feasible.

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1279-1287]

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


12. Automerge override bypass ✓ Resolved 🐞 Bug ⛨ Security
Description
The suspicious-path override in set_pull_request_automerge() only flips a local auto_merge flag and
posts a comment; it never disables an already-enabled PR auto-merge state. Additionally, this
auto-merge logic is not invoked for the pull_request "synchronize" action, so a PR can gain
suspicious-path changes after auto-merge was enabled without being turned off.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1246-1266]

+        # Security override: block auto-merge if PR modifies suspicious paths
+        if auto_merge and self.github_webhook.security_suspicious_paths:
+            changed_files = self.owners_file_handler.changed_files
+            suspicious_matches = [
+                f
+                for f in changed_files
+                if any(f.startswith(prefix) for prefix in self.github_webhook.security_suspicious_paths)
+            ]
+            if suspicious_matches:
+                auto_merge = False
+                files_list = ", ".join(f"`{f}`" for f in suspicious_matches)
+                self.logger.info(
+                    f"{self.log_prefix} Auto-merge blocked: PR modifies security-sensitive paths: {suspicious_matches}"
+                )
+                await github_api_call(
+                    pull_request.create_issue_comment,
+                    f"Auto-merge blocked: PR modifies security-sensitive paths: {files_list}",
+                    logger=self.logger,
+                    log_prefix=self.log_prefix,
+                )
+
Evidence
The synchronize path returns without calling set_pull_request_automerge(), while
opened/reopened/ready_for_review explicitly calls it. Inside set_pull_request_automerge(),
suspicious-path detection only toggles a local flag and comments; unlike the existing
AI-resolved-conflicts block, it never calls disable_automerge() when suspicious files are present.

webhook_server/libs/handlers/pull_request_handler.py[234-333]
webhook_server/libs/handlers/pull_request_handler.py[1231-1285]

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

### Issue description
Auto-merge blocking for suspicious paths is incomplete:
1) When suspicious paths are detected, the code sets a local `auto_merge = False` but does **not** call `pull_request.disable_automerge()` when auto-merge is already enabled on the PR.
2) The handler does not run `set_pull_request_automerge()` on `pull_request` `synchronize` events, so suspicious changes added after initial enablement can keep auto-merge active.

### Issue Context
This PR introduces a security invariant: *PRs touching suspicious paths must never be auto-merged*. The current implementation only prevents enabling auto-merge in the current call.

### Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[234-333]
- webhook_server/libs/handlers/pull_request_handler.py[1231-1305]

### Suggested implementation sketch
- Extract a helper like `_apply_suspicious_paths_automerge_override(pull_request)` that:
 - Computes `suspicious_matches`.
 - If matches exist:
   - If `pull_request.raw_data.get("auto_merge")` is truthy, call `pull_request.disable_automerge` via `github_api_call` (with exception handling similar to the AI-resolved conflicts block).
   - Post a single explanatory comment (optional: avoid duplicate comments).
   - Ensure the caller does not re-enable auto-merge.
- Invoke this helper from both:
 - `set_pull_request_automerge()` (before enabling)
 - the `hook_action == "synchronize"` flow (so updates are enforced without necessarily enabling auto-merge on every sync).

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


13. security-checks ignores repo config ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
security-checks is loaded without passing repository_config into Config.get_value(), so
.github-webhook-server.yaml (in-repo) overrides are ignored for suspicious paths and committer
identity enablement. This breaks the required 3-level config resolution behavior for the new
security checks.
Code

webhook_server/libs/github_api.py[R876-883]

+        _security_checks: dict[str, Any] | None = self.config.get_value(value="security-checks", return_on_none=None)
+        _security_config = _security_checks if isinstance(_security_checks, dict) else {}
+        _suspicious_paths = _security_config.get("suspicious-paths", DEFAULT_SUSPICIOUS_PATHS)
+        self.security_suspicious_paths: list[str] = (
+            _suspicious_paths if isinstance(_suspicious_paths, list) else DEFAULT_SUSPICIOUS_PATHS
+        )
+        self.security_committer_identity_check: bool = _security_config.get("committer-identity-check", True)
+
Evidence
Config.get_value() only consults the in-repo config when the caller passes it via extra_dict;
the new security-checks load path omits extra_dict=repository_config, so resolved values cannot
come from .github-webhook-server.yaml, violating the 3-level config requirement.

Security-checks configuration is loaded and merged at all 3 config levels
webhook_server/libs/github_api.py[876-883]
webhook_server/libs/config.py[132-153]

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

## Issue description
`security-checks` is currently read using `self.config.get_value(...)` without `extra_dict=repository_config`, which prevents in-repo `.github-webhook-server.yaml` from overriding security check settings.

## Issue Context
`_repo_data_from_config(repository_config=...)` is called twice: first with `{}` (server config only), then with the parsed `.github-webhook-server.yaml` content. Most config keys pass `extra_dict=repository_config` to allow in-repo overrides, but `security-checks` currently does not.

## Fix Focus Areas
- webhook_server/libs/github_api.py[876-883]

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



Remediation recommended

14. Automerge dedupe scans all comments 🐞 Bug ➹ Performance
Description
set_pull_request_automerge() uses list(pull_request.get_issue_comments()) to dedupe the
“Auto-merge blocked” comment, which forces retrieval/materialization of all PR comments whenever
this path runs. On PRs with many comments or frequent events, this adds avoidable API load and
latency.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1288-1292]

+                    existing_comments = await github_api_call(
+                        lambda: list(pull_request.get_issue_comments()),
+                        logger=self.logger,
+                        log_prefix=self.log_prefix,
+                    )
Evidence
The new code explicitly materializes all comments into a list for dedupe; elsewhere in the same
file, comment existence checks are implemented in a short-circuiting way without list(...),
demonstrating a cheaper approach.

webhook_server/libs/handlers/pull_request_handler.py[1258-1297]
webhook_server/libs/handlers/pull_request_handler.py[1902-1911]

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 suspicious-path auto-merge block dedupe logic fetches/materializes all PR issue comments via `list(pull_request.get_issue_comments())` before scanning them. This can be expensive for comment-heavy PRs and is unnecessary when we only need to know whether *a single* matching bot-authored comment exists.

## Issue Context
The same module already contains a pattern that can short-circuit while iterating comments (`any(... for comment in pull_request.get_issue_comments())`).

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1287-1297]
- webhook_server/libs/handlers/pull_request_handler.py[1902-1911]

## Suggested fix
Replace the list materialization with a short-circuiting scan, e.g.:
- define a small predicate and use `any(...)` over the `PaginatedList` iterator
- (optional) limit to recent comments only if feasible with PyGithub paging (`get_page(0)` / first page), since the bot comment will typically be recent
Keep the existing author check (`c.user.login == self.github_webhook.api_user`) to avoid spoofing.

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


15. Dedup breaks on token rotation 🐞 Bug ☼ Reliability
Description
The auto-merge-block comment deduplication only treats an existing comment as a duplicate if it was
authored by the *currently selected* api_user, so when the server rotates between multiple
configured GitHub tokens/users across webhook events it can post repeated "Auto-merge blocked"
comments on the same PR. This creates unnecessary PR noise and burns GitHub API quota despite the
code intending to avoid duplicates.
Code

webhook_server/libs/handlers/pull_request_handler.py[R1293-1296]

+                    already_commented = any(
+                        c.body.startswith("Auto-merge blocked: PR modifies security-sensitive paths:")
+                        and c.user.login == self.github_webhook.api_user
+                        for c in existing_comments
Evidence
The dedup check requires the existing comment’s author to match self.github_webhook.api_user, but
api_user is selected per webhook instance by choosing the token/user with the highest remaining
rate limit, so the author identity can legitimately change between events and defeat deduplication.

webhook_server/libs/handlers/pull_request_handler.py[1287-1297]
webhook_server/libs/github_api.py[139-143]
webhook_server/utils/helpers.py[466-536]

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

## Issue description
`set_pull_request_automerge()` deduplicates the "Auto-merge blocked" comment by requiring `c.user.login == self.github_webhook.api_user`. In multi-token setups the webhook can pick a different token/user on later events (rate-limit selection), so an earlier bot-authored comment won’t be recognized and the same message can be posted again.

## Issue Context
- `GithubWebhook.api_user` is derived from `get_api_with_highest_rate_limit(...)` and may differ between webhook instances when multiple tokens are configured.
- The dedup predicate should be stable across token/user rotation while still avoiding the previously-fixed spoofing issue.

## Fix Focus Areas
- webhook_server/libs/handlers/pull_request_handler.py[1293-1296]

## Suggested fix approach
- Track *all* configured API users (e.g., during startup / config load, resolve logins for each configured token once) and dedup against membership in that set, not only the currently-selected `api_user`.
 - e.g., `self.github_webhook.api_users_all: set[str]` and then `c.user.login in api_users_all`.
- Alternatively, embed a stable hidden marker in the comment body (HTML comment) and dedup by the marker + author type, so token rotation doesn’t matter while still preventing user spoofing.

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


16. Unescaped paths in output 🐞 Bug ◔ Observability
Description
run_security_suspicious_paths() interpolates changed file paths and configured prefixes directly
into Markdown code spans without escaping. Filenames/prefixes containing backticks or control
characters can break the rendered check output and make the report confusing or misleading.
Code

webhook_server/libs/handlers/runner_handler.py[R333-347]

+            if matched_files:
+                files_list = "\n".join(f"- `{f}`" for f in matched_files)
+                output: CheckRunOutput = {
+                    "title": "\u274c Security: Suspicious Paths Detected",
+                    "summary": f"{len(matched_files)} file(s) modify security-sensitive paths",
+                    "text": (
+                        f"## Suspicious Path Detection\n\n"
+                        f"This PR modifies files in security-sensitive locations:\n\n"
+                        f"{files_list}\n\n"
+                        f"**Configured suspicious path prefixes:**\n"
+                        + "\n".join(f"- `{p}`" for ...

@myakove-bot

Copy link
Copy Markdown
Collaborator

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: Disabled for this repository
  • Pre-commit Checks: pre-commit runs automatically if .pre-commit-config.yaml exists
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified
  • Labels: All label categories are enabled (default configuration)

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status
  • /reprocess - Trigger complete PR workflow reprocessing (useful if webhook failed or configuration changed)
  • /regenerate-welcome - Regenerate this welcome message

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /automerge - Enable automatic merging when all requirements are met (maintainers and approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest build-container - Rebuild and test container image
  • /retest python-module-install - Test Python package installation
  • /retest pre-commit - Run pre-commit hooks and checks
  • /retest conventional-title - Validate commit message format
  • /retest all - Run all available tests

Container Operations

  • /build-and-push-container - Build and push container image (tagged with PR number)
    • Supports additional build arguments: /build-and-push-container --build-arg KEY=value

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 1 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No wip, hold, has-conflicts labels and PR must be mergeable (no conflicts)
  5. Verified: PR must be marked as verified

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge
AI Features
  • Conventional Title: Mode: fix (claude/claude-opus-4-6[1m])
  • Cherry-Pick Conflict Resolution: Enabled (claude/claude-opus-4-6[1m])
  • Test Oracle: Triggers: approved (claude/claude-opus-4-6[1m]); /test-oracle can be used anytime

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is removed on new commits unless the push is detected as a clean rebase
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Container Builds: Container images are automatically tagged with the PR number
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

Comment thread webhook_server/libs/github_api.py Outdated
Comment thread examples/config.yaml
Comment thread webhook_server/libs/handlers/pull_request_handler.py
Comment thread webhook_server/libs/handlers/runner_handler.py
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a537bcb

Comment thread webhook_server/libs/handlers/pull_request_handler.py Outdated
Comment thread webhook_server/libs/github_api.py Outdated
@myakove

myakove commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/handlers/pull_request_handler.py:1246 (qodo bug) — Automerge override bypass

Addressed: Fixed — calls disable_automerge when suspicious paths detected and auto-merge already enabled.

webhook_server/libs/github_api.py:876 (qodo requirement gap) — security-checks ignores repo config

Addressed: By design — security-checks intentionally not overridable by in-repo config to prevent attackers from weakening security policy. Documented in issue #1106.

examples/config.yaml:139 (qodo requirement gap) — In-repo example lacks security-checks

Addressed: Added note in examples/.github-webhook-server.yaml that security-checks is global-only config.

@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c079464

Comment thread examples/.github-webhook-server.yaml Outdated
Comment thread webhook_server/libs/handlers/pull_request_handler.py
Comment thread webhook_server/tests/test_security_checks.py Outdated
@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit a6f91fd

@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4ad9062

@myakove

myakove commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

examples/.github-webhook-server.yaml:4 (qodo requirement gap) — Doc says security-checks server-only

Addressed: By design — security-checks is global-only to prevent attackers from weakening security policy via in-repo config.

webhook_server/libs/handlers/pull_request_handler.py:1246 (qodo bug) — Automerge override bypass

Addressed: Previously addressed.

webhook_server/libs/github_api.py:876 (qodo requirement gap) — security-checks ignores repo config

Addressed: By design — intentionally not using extra_dict for security-checks. Documented in issue #1106.

webhook_server/tests/test_security_checks.py:506 (qodo bug) — Tautological sanitization tests

Addressed: Previously addressed.

webhook_server/libs/github_api.py:885 (qodo bug) — Mandatory flag unvalidated

Addressed: Fixed in commit a6f91fd — added bool() cast to ensure non-bool config values are coerced correctly.

examples/config.yaml:139 (qodo requirement gap) — In-repo example lacks security-checks

Addressed: Previously addressed.

Comment thread webhook_server/libs/handlers/pull_request_handler.py Outdated
Comment thread webhook_server/libs/handlers/pull_request_handler.py Outdated
Comment thread webhook_server/tests/test_security_checks.py
@myakove

myakove commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo requirement gap) — security-override allows auto-merge

Addressed: By design — /security-override is a maintainer-only command that explicitly allows bypassing security checks. The entire purpose of the command is to let maintainers proceed when a legitimate change touches security-sensitive paths. The label is only addable via the /security-override command which validates maintainer permissions.

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override label auth bypass

Addressed: Acknowledged as a defense-in-depth concern. The /security-override command validates maintainer permissions before adding the label. Direct label application via GitHub UI requires write access (already restricted to collaborators). Adding label provenance verification via timeline events would add significant API cost. Accepted risk for now — documenting that the label should only be applied via the command.

webhook_server/tests/test_security_checks.py:382 (qodo bug) — Automerge test mock breaks

Addressed: Test issue — not a production bug. Mock fixtures updated to match new label-checking behavior.

@qodo-code-review

qodo-code-review Bot commented Jun 9, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit b6cafbe

@qodo-code-review

qodo-code-review Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit b4d7199

…otation

Check c.user.login against auto_verified_and_merged_users (all configured
API user logins) instead of just the current api_user. In multi-token setups,
a different token may have posted the original comment. This makes dedup
stable across token rotation while still preventing user spoofing.
Comment thread examples/.github-webhook-server.yaml
Comment thread webhook_server/libs/handlers/pull_request_handler.py
@qodo-code-review

qodo-code-review Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 80dddd8

@myakove

myakove commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override bypass via stale checks

Not addressed: Already addressed — actual code uses security_path_runs[0].conclusion (newest check run), NOT any(). Fixed in commit 72801ca.

webhook_server/libs/handlers/pull_request_handler.py:1293 (qodo bug) — Dedup breaks on token rotation

Addressed: Fixed in commit 80dddd8 — dedup now checks auto_verified_and_merged_users (all configured API users) instead of single api_user.

webhook_server/libs/handlers/runner_handler.py:333 (qodo bug) — Unescaped paths in output

Not addressed: Git filenames cannot contain backticks or newlines. Paths from git diff --name-only are valid git paths. No practical vector.

webhook_server/libs/github_api.py:880 (qodo rule violation) — suspicious-paths silently defaulted

Not addressed: Optional config with schema-validated defaults — standard codebase pattern.

@myakove

myakove commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

examples/.github-webhook-server.yaml:175 (qodo requirement gap) — Example suspicious paths incomplete

Not addressed: By design — in-repo example shows override subset. Full defaults in examples/config.yaml.

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override bypass via stale checks

Not addressed: Already addressed — actual code uses security_path_runs[0].conclusion (newest), not any(). Fixed in commit 72801ca.

webhook_server/libs/handlers/pull_request_handler.py:1288 (qodo bug) — Automerge dedupe scans all comments

Not addressed: Current pattern is correct — list() inside github_api_call is the non-blocking approach per AGENTS.md. any() over PaginatedList would block.

webhook_server/libs/handlers/pull_request_handler.py:1293 (qodo bug) — Dedup breaks on token rotation

Addressed: Already fixed in commit 80dddd8 — dedup checks auto_verified_and_merged_users (all API users) instead of single api_user.

webhook_server/libs/handlers/runner_handler.py:333 (qodo bug) — Unescaped paths in output

Not addressed: Git filenames cannot contain backticks. Paths from git diff --name-only are valid git paths. No practical vector.

@myakove

myakove commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

examples/.github-webhook-server.yaml:175 (qodo requirement gap) — Example suspicious paths incomplete

Not addressed: By design — in-repo example shows override subset. Full defaults in examples/config.yaml.

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override bypass via stale checks

Not addressed: Already addressed — actual code uses security_path_runs[0].conclusion (newest), not any(). Fixed in commit 72801ca.

webhook_server/libs/handlers/pull_request_handler.py:1288 (qodo bug) — Automerge dedupe scans all comments

Not addressed: Current list() inside github_api_call is the correct non-blocking pattern per AGENTS.md. any() over PaginatedList would block the event loop.

webhook_server/libs/handlers/pull_request_handler.py:1293 (qodo bug) — Dedup breaks on token rotation

Addressed: Already fixed in commit 80dddd8 — dedup checks auto_verified_and_merged_users instead of single api_user.

webhook_server/libs/handlers/runner_handler.py:333 (qodo bug) — Unescaped paths in output

Not addressed: Git filenames cannot contain backticks. No practical vector.

@myakove

myakove commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

examples/.github-webhook-server.yaml:175 (qodo requirement gap) — Example suspicious paths incomplete

Not addressed: By design — in-repo example shows override subset. Full defaults in examples/config.yaml.

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override bypass via stale checks

Not addressed: Stale code_diff — actual code uses security_path_runs[0].conclusion (newest), not any().

webhook_server/libs/handlers/pull_request_handler.py:1288 (qodo bug) — Automerge dedupe scans all comments

Not addressed: Current list() inside github_api_call is the correct non-blocking pattern.

webhook_server/libs/handlers/pull_request_handler.py:1293 (qodo bug) — Dedup breaks on token rotation

Addressed: Already fixed in commit 80dddd8 — dedup checks auto_verified_and_merged_users.

webhook_server/libs/handlers/runner_handler.py:333 (qodo bug) — Unescaped paths in output

Not addressed: Git filenames cannot contain backticks. No practical vector.

@myakove

myakove commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

examples/.github-webhook-server.yaml:175 (qodo requirement gap) — Example suspicious paths incomplete

Not addressed: By design — in-repo example shows override subset. Full defaults in examples/config.yaml.

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override bypass via stale checks

Not addressed: Stale code_diff — actual code uses security_path_runs[0].conclusion (newest), not any().

webhook_server/libs/handlers/pull_request_handler.py:1288 (qodo bug) — Automerge dedupe scans all comments

Not addressed: Current list() inside github_api_call is the correct non-blocking pattern.

webhook_server/libs/handlers/pull_request_handler.py:1293 (qodo bug) — Dedup breaks on token rotation

Addressed: Already fixed in commit 80dddd8.

webhook_server/libs/handlers/runner_handler.py:333 (qodo bug) — Unescaped paths in output

Not addressed: Git filenames cannot contain backticks. No practical vector.

@qodo-code-review

qodo-code-review Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit b276a46

Comment thread webhook_server/libs/github_api.py
Comment thread webhook_server/libs/github_api.py Outdated
Comment thread webhook_server/libs/handlers/issue_comment_handler.py
@myakove

myakove commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@qodo-code-review[bot]

The following review comments were reviewed and a decision was made:

webhook_server/libs/github_api.py:957 (qodo requirement gap) — security-checks not merged

Not addressed: Already replied — see previous consolidated comments.

webhook_server/libs/github_api.py:967 (qodo requirement gap) — committer-identity-check type unchecked

Not addressed: Already replied — see previous consolidated comments.

examples/.github-webhook-server.yaml:175 (qodo requirement gap) — Example suspicious paths incomplete

Not addressed: Already replied — see previous consolidated comments.

webhook_server/libs/handlers/pull_request_handler.py:1268 (qodo bug) — Override bypass via stale checks

Not addressed: Already replied — see previous consolidated comments.

webhook_server/libs/handlers/pull_request_handler.py:1288 (qodo bug) — Automerge dedupe scans all comments

Not addressed: Already replied — see previous consolidated comments.

webhook_server/libs/handlers/pull_request_handler.py:1293 (qodo bug) — Dedup breaks on token rotation

Not addressed: Already replied — see previous consolidated comments.

webhook_server/libs/handlers/runner_handler.py:333 (qodo bug) — Unescaped paths in output

Not addressed: Already replied — see previous consolidated comments.

@qodo-code-review

qodo-code-review Bot commented Jun 10, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4ac55ae

@myakove myakove merged commit 79427a2 into main Jun 10, 2026
6 of 8 checks passed
@myakove myakove deleted the feat/issue-1106-pr-security-checks branch June 10, 2026 18:15
@myakove-bot

Copy link
Copy Markdown
Collaborator

New container for ghcr.io/myk-org/github-webhook-server:latest published

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: PR security checks — suspicious paths, committer identity, auto-merge override

2 participants