Skip to content

feat(#568): make protected paths configurable via env var - #569

Open
ralphbean wants to merge 22 commits into
mainfrom
agent/568-configurable-protected-paths
Open

feat(#568): make protected paths configurable via env var#569
ralphbean wants to merge 22 commits into
mainfrom
agent/568-configurable-protected-paths

Conversation

@ralphbean

@ralphbean ralphbean commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds REVIEW_PROTECTED_PATHS environment variable to override the hardcoded protected-path list in post-review.sh. Comma-separated path prefixes, whitespace-trimmed.
  • Wires REVIEW_FINDING_SEVERITY_THRESHOLD into harness/review.yaml's runner env. post-review.sh already read it on the runner side, but it was never plumbed through runner_env — only the sandbox got it via env/review.env. So runner-side severity filtering silently defaulted to low before this PR.
  • Scope increase: the original design had a separate env/default-review-protected-paths.txt file that post-review.sh, run-fullsend.sh, and SKILL.md each had to independently resolve via a three-way (set / set-empty / unset) ladder. That's now replaced with a single literal default declared directly in harness/review.yaml's env.runner/env.sandbox stanzas (matching the existing constant-value pattern already used for things like MAX_RETRIES in harness/fix.yaml). Repos needing a different list override it via harness composition instead of an env var. Unset is now a hard misconfiguration error rather than a file-read fallback.
    • Deleted env/default-review-protected-paths.txt.
    • post-review.sh: collapsed to two cases (non-empty / explicitly-empty).
    • run-fullsend.sh: removed the now-dead default-computation block.
    • Updated SKILL.md, docs/review.md, and the 003-protected-path-downgrade eval case's annotations to match.
  • Updates skills/pr-review/SKILL.md and docs/review.md to document the variable.

Closes #568

Test plan

  • All 69 tests pass (bash scripts/post-review-test.sh)
  • Schema validation tests pass (bash scripts/validate-output-schema-test.sh)
  • pre-commit (shellcheck, yaml, secrets) clean on all changed files

🤖 Generated with Claude Code

@ralphbean
ralphbean requested a review from a team as a code owner July 30, 2026 16:57
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Make protected review paths configurable via REVIEW_PROTECTED_PATHS

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Add REVIEW_PROTECTED_PATHS env var to override the default protected-path list.
• Preserve existing defaults when the variable is unset; trim whitespace in overrides.
• Add integration tests and docs covering override behavior and examples.
Diagram

graph TD
  A["CI env: REVIEW_PROTECTED_PATHS"] --> B["scripts/post-review.sh"] --> C["Protected path match"] --> D["Downgrade approve to comment"]
  B --> E["gh pr view --json files"] --> F["PR file list"] --> C
  T["scripts/post-review-test.sh"] --> B
  G["Docs (review.md, SKILL.md)"] --> A
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Additive override (extend defaults)
  • ➕ Keeps baseline protections while allowing repos to add extra paths safely
  • ➕ Avoids accidental weakening by replacing the list entirely
  • ➖ Harder to express 'remove one default' without additional syntax
  • ➖ Slightly more complex UX and parsing logic
2. Config file in repo (e.g., .review/protected-paths)
  • ➕ Versioned, reviewable changes to protections
  • ➕ More discoverable than CI env configuration
  • ➖ Protected-path logic becomes self-referential (changing config could be protected)
  • ➖ More moving parts for CI/setup across repos
3. Support glob patterns (not just prefixes)
  • ➕ More expressive matching (e.g., **/workflows/*.yml)
  • ➕ Can reduce false positives/negatives for edge cases
  • ➖ More complex matching semantics and escaping rules in bash
  • ➖ Higher risk of misconfiguration compared to simple prefix rules

Recommendation: The PR’s env-var replacement approach is the simplest operationally for CI and keeps default behavior unchanged when unset. If teams are likely to want to add protections more often than replace them, consider a follow-up to support an additive mode (e.g., REVIEW_PROTECTED_PATHS_MODE=extend) while keeping the current replacement behavior as the explicit override.

Files changed (4) +114 / -9

Enhancement (1) +12 / -2
post-review.shMake protected paths configurable via REVIEW_PROTECTED_PATHS +12/-2

Make protected paths configurable via REVIEW_PROTECTED_PATHS

• Renames the hardcoded array to 'DEFAULT_PROTECTED_PATHS' and introduces runtime selection of 'PROTECTED_PATHS'. When 'REVIEW_PROTECTED_PATHS' is set, parses it as a comma-separated list and trims whitespace before matching; otherwise uses defaults.

scripts/post-review.sh

Tests (1) +92 / -2
post-review-test.shAdd integration tests for protected-path overrides +92/-2

Add integration tests for protected-path overrides

• Allows the mocked 'gh pr view --json files' response to be set via 'MOCK_PR_FILES'. Adds a helper and five integration tests verifying default behavior, override replacement semantics, whitespace trimming, and non-matching behavior.

scripts/post-review-test.sh

Documentation (2) +10 / -5
review.mdDocument REVIEW_PROTECTED_PATHS in the variables table +4/-3

Document REVIEW_PROTECTED_PATHS in the variables table

• Adds 'REVIEW_PROTECTED_PATHS' to the documented CI variables, describing comma-separated prefix semantics and default behavior. Tweaks surrounding wording to reflect multiple variables and clarifies severity-filtering downgrade text.

docs/review.md

SKILL.mdExplain how to override the protected paths list +6/-2

Explain how to override the protected paths list

• Updates the protected-paths section to describe the default list as such and documents the 'REVIEW_PROTECTED_PATHS' env var override behavior. Adjusts guidance text to reference the active protected paths list rather than only defaults.

skills/pr-review/SKILL.md

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 4:59 PM UTC · Ended 5:07 PM UTC
Commit: c8f60ed · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. scripts/ and skills/ modified ✓ Resolved 📜 Skill insight § Compliance
Description
This PR changes files under protected governance/infrastructure paths (scripts/, skills/). Per
policy, PRs touching protected paths must not be auto-approved and require human approval.
Code

scripts/post-review.sh[R188-196]

+if [[ -n "${REVIEW_PROTECTED_PATHS:-}" ]]; then
+  IFS=',' read -ra PROTECTED_PATHS <<< "${REVIEW_PROTECTED_PATHS}"
+  # Trim leading/trailing whitespace from each entry.
+  for i in "${!PROTECTED_PATHS[@]}"; do
+    PROTECTED_PATHS[i]="$(echo "${PROTECTED_PATHS[i]}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
+  done
+else
+  PROTECTED_PATHS=("${DEFAULT_PROTECTED_PATHS[@]}")
+fi
Relevance

●●● Strong

Protected-path governance is core review policy; team documents/maintains manual-review labeling for
such cases.

PR-#389

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1538392 requires that PRs modifying protected paths (including scripts/ and
skills/) must not be auto-approved and must raise a finding. The diff includes changes to
scripts/post-review.sh, scripts/post-review-test.sh, and skills/pr-review/SKILL.md, which are
within the protected path set.

scripts/post-review.sh[161-196]
scripts/post-review-test.sh[945-1034]
skills/pr-review/SKILL.md[986-1019]
Skill: pr-review

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

## Issue description
This PR modifies protected governance/infrastructure paths (e.g., `scripts/`, `skills/`), which must not be auto-approved.

## Issue Context
The compliance requirement mandates raising a protected-path finding and ensuring a human reviewer explicitly approves the PR when protected paths are touched.

## Fix Focus Areas
- scripts/post-review.sh[188-196]
- scripts/post-review-test.sh[945-1034]
- skills/pr-review/SKILL.md[986-1019]

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



Remediation recommended

2. Empty prefix matches all ✓ Resolved 🐞 Bug ☼ Reliability
Description
If REVIEW_PROTECTED_PATHS contains an empty/whitespace-only entry (e.g., ",deploy/" or "deploy/,
,manifests/"), the trim step leaves an empty string in PROTECTED_PATHS. The later check `[[ "$file"
== "$pattern"* ]] then matches every file when pattern is empty, so all approve` actions are
downgraded regardless of what the PR touches.
Code

scripts/post-review.sh[R188-193]

+if [[ -n "${REVIEW_PROTECTED_PATHS:-}" ]]; then
+  IFS=',' read -ra PROTECTED_PATHS <<< "${REVIEW_PROTECTED_PATHS}"
+  # Trim leading/trailing whitespace from each entry.
+  for i in "${!PROTECTED_PATHS[@]}"; do
+    PROTECTED_PATHS[i]="$(echo "${PROTECTED_PATHS[i]}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
+  done
Relevance

●●● Strong

Deterministic bug: empty trimmed entry becomes empty prefix and matches all; team usually accepts
hardening fixes.

PR-#284
PR-#38

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The script trims each comma-split entry but never filters entries that become empty; later it
performs a Bash prefix/glob match against each entry, and an empty prefix matches any path, causing
protected matches for all files.

scripts/post-review.sh[188-196]
scripts/post-review.sh[206-214]

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

## Issue description
`REVIEW_PROTECTED_PATHS` is split and whitespace-trimmed, but empty entries are not removed. Any empty entry (from consecutive commas, leading comma, or whitespace-only segment) makes the protected-path match treat *all* files as protected.

## Issue Context
This is configuration-dependent and fail-closed (it downgrades approvals), but it can unexpectedly disable automated approvals across the repo.

## Fix Focus Areas
- scripts/post-review.sh[188-196]
- scripts/post-review.sh[206-214]

## Suggested fix
- After trimming, rebuild `PROTECTED_PATHS` with only non-empty entries.
- If the resulting list is empty, fail closed in a predictable way (e.g., log an error and fall back to `DEFAULT_PROTECTED_PATHS`, or exit non-zero), to avoid accidentally disabling protection.
- Add a regression test for malformed inputs like `,deploy/` and `deploy/, ,manifests/`.

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


Grey Divider

Context used
✅ Compliance rules (platform): 55 rules
✅ Skills: 4 invoked
  code-review
  code-implementation
  pr-review
  docs-review

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread scripts/post-review.sh Outdated
Comment thread scripts/post-review.sh Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:08 PM UTC · Ended 5:13 PM UTC
Commit: df953d2 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:15 PM UTC · Ended 5:23 PM UTC
Commit: d9888a5 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 5:24 PM UTC · Ended 5:47 PM UTC
Commit: 9278aab · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:48 PM UTC · Completed 6:08 PM UTC
Commit: bcf097f · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md — PR modifies files under protected paths: harness/, scripts/, skills/. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

  • [edge-case] harness/review.yaml:57 — The default REVIEW_PROTECTED_PATHS value adds env/ as a new protected path prefix that was NOT in the previous hardcoded list in post-review.sh. The PR body acknowledges "Scope increase" for this change, but it is a behavioral change: any PR touching env/ files will now trigger a protected-path downgrade where it previously would not. See also: [unauthorized-change] finding at this location.
    Remediation: If adding env/ is intentional (to protect configuration env files like env/review.env), document the rationale explicitly. If unintentional, remove env/ from the default value in harness/review.yaml.

  • [unauthorized-change] harness/review.yaml — Issue Review agent: make protected paths configurable via environment variable #568 requests making protected paths configurable but does not authorize adding env/ as a new entry to the default list. The PR body acknowledges this as a "Scope increase." See also: [edge-case] finding at this location.
    Remediation: Remove env/ from the default list or obtain explicit authorization for adding it.

  • [stale-reference] agents/code.md:76 — States "Protected paths are defined in post-review.sh." After this PR, the default protected-path list is defined in harness/review.yaml, with post-review.sh serving as the enforcement point.
    Remediation: Update to: "Protected paths are configured in harness/review.yaml and enforced by post-review.sh."

  • [runtime-mechanism] skills/pr-review/sub-agents/security-triage.md:40 — The hardcoded governance paths list was removed and replaced with a dependency on the orchestrator injecting an "Active governance paths" section in the spawn prompt. If the orchestrator omits or malforms this section, the sub-agent silently loses all governance-path classification capability with no fallback or error detection.
    Remediation: Add a defensive instruction to security-triage.md with a minimal fallback list (e.g., .claude/, .github/, agents/, scripts/, harness/, skills/) to use when no Active governance paths section is present.

Low

  • [GHA-workflow-command-injection] scripts/post-review.sh:210 — The ::error:: workflow command at the degenerate-paths abort sanitizes the interpolated REVIEW_PROTECTED_PATHS value using ::: collapse, which is not idempotent (:::::). The same file's REVIEW_FINDING_SEVERITY_THRESHOLD sanitization (lines ~108–111) uses the more robust per-character stripping pattern (//%/ and //:/). Exploitability is limited (requires CI config write access).
    Remediation: Use per-character stripping matching the existing pattern in the same file.

  • [fail-open-risk] harness/review.yaml:49 — Setting REVIEW_PROTECTED_PATHS to an empty string disables all protected-path enforcement with a ::notice:: log message. This is a deliberate design decision (explicit opt-out), not an accidental bypass.

  • [stale-hardcoded-list] agents/fix.md:85 — Contains a hardcoded protected paths list (lines 85–102) that will diverge from the canonical list in harness/review.yaml because the harness list now includes env/. Impact is limited to LLM guidance accuracy.
    Remediation: Update agents/fix.md lines 85–102 to add the missing env/ entry.

  • [stale-reference] agents/fix.md:105 — States "Protected-path enforcement lives in post-review.sh" without noting that the path list configuration source has changed. The statement remains true but is incomplete.
    Remediation: Clarify that paths are now configured via the REVIEW_PROTECTED_PATHS env var (defaults in harness/review.yaml).

  • [scope-exceeded] eval/scripts/run-fullsend.sh — Adds REVIEW_FINDING_SEVERITY_THRESHOLD passthrough, a separate concern from the protected-path configurability requested in issue Review agent: make protected paths configurable via environment variable #568. Minor supporting change for the new eval case.

  • [design-direction] scripts/post-review.sh — The implementation introduces an explicit "disable all protection" mode (empty string) that did not exist in the hardcoded implementation. The behavior is documented in the PR's docs/review.md update and tested.

  • [variable-naming-consistency] scripts/post-review.sh — New temporary variables use leading underscores (_trimmed, _entry, _sanitized_paths) while existing codebase convention uses lowercase without leading underscores (stale_label, file, threshold_rank).

Previous run

Review

Findings

Medium

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md — PR modifies files under protected paths: harness/, scripts/, skills/. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

  • [edge-case] harness/review.yaml:57 — The default REVIEW_PROTECTED_PATHS value adds env/ as a new protected path prefix that was NOT in the previous hardcoded list in post-review.sh. The PR body acknowledges "Scope increase" for this change, but it is a behavioral change: any PR touching env/ files will now trigger a protected-path downgrade where it previously would not.
    Remediation: If adding env/ is intentional (to protect configuration env files like env/review.env), document it explicitly in the PR description. If unintentional, remove env/ from the default value in harness/review.yaml.

Low

  • [GHA-workflow-command-injection] scripts/post-review.sh:210 — The ::error:: workflow command at the degenerate-paths abort sanitizes the interpolated REVIEW_PROTECTED_PATHS value for literal newlines, carriage returns, and :: sequences, but does not strip %0A/%0D URL-encoded newlines, which GitHub Actions decodes in workflow command parameters. Exploitability is limited (requires CI config write access to inject a crafted value).
    Remediation: Add %0A and %0D stripping (case-insensitive variants %0a/%0d too) to the sanitization block.

  • [fail-open-risk] harness/review.yaml:56 — Setting REVIEW_PROTECTED_PATHS to an empty string disables all protected-path enforcement with a ::notice:: log message. This is a deliberate design decision (explicit opt-out), not an accidental bypass. The risk is low because the value is a literal in harness/review.yaml, not user-supplied input — accidental clearing requires an intentional harness composition override.

  • [stale-reference] agents/code.md:76 — States "Protected paths are defined in post-review.sh." After this PR, the default protected-path list is defined in harness/review.yaml, with post-review.sh serving as the enforcement point. The reference is partially stale.
    Remediation: Update to: "Protected paths are configured in harness/review.yaml and enforced by post-review.sh."

  • [stale-hardcoded-list] agents/fix.md:85agents/fix.md contains a hardcoded protected paths list (lines 85–102) that will diverge from the canonical list in harness/review.yaml because the harness list now includes env/. Impact is limited to LLM guidance accuracy.
    Remediation: Update agents/fix.md lines 85–102 to add the missing env/ entry, matching the canonical list in harness/review.yaml.

Previous run (2)

Review

Findings

Medium

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md — PR modifies files under protected paths: harness/, scripts/, skills/. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [GHA-workflow-command-injection] scripts/post-review.sh:210 — The ::error:: workflow command at the degenerate-paths abort sanitizes the interpolated REVIEW_PROTECTED_PATHS value for literal newlines, carriage returns, and :: sequences, but does not strip %0A/%0D URL-encoded newlines, which GitHub Actions decodes in workflow command parameters. Exploitability is limited (requires CI config write access to inject a crafted value).
    Remediation: Add %0A and %0D stripping (case-insensitive variants %0a/%0d too) to the sanitization block.

  • [stale-reference] agents/code.md:76 — States "Protected paths are defined in post-review.sh." After this PR, the default protected-path list is defined as a literal value in harness/review.yaml, with post-review.sh serving as the enforcement point. The statement is misleading.
    Remediation: Update to: "Protected paths are configured in harness/review.yaml and enforced by post-review.sh."

  • [stale-hardcoded-list] agents/fix.md:85agents/fix.md contains a hardcoded protected paths list (lines 85–102) that is now stale. The canonical list moved to harness/review.yaml. The harness list includes env/ which is absent from agents/fix.md. Impact is limited to LLM guidance accuracy.
    Remediation: Update agents/fix.md lines 85–102 to add the missing env/ entry, matching the canonical list in harness/review.yaml.

  • [architectural-conflict] harness/review.yaml:173REVIEW_PROTECTED_PATHS uses a literal default baked into harness YAML, while REVIEW_FINDING_SEVERITY_THRESHOLD uses a ${VAR} passthrough with a script-side default in post-review.sh. The inconsistency is intentional (the paths list must always be set; the threshold has a trivial scalar fallback), but the two configuration patterns are undocumented.
    Remediation: Add a comment in harness/review.yaml or docs/review.md explaining why the two variables use different default-provision patterns.

  • [scope-creep] env/default-review-protected-paths.txt — The PR deletes env/default-review-protected-paths.txt and replaces it with a literal baked into harness/review.yaml. Issue Review agent: make protected paths configurable via environment variable #568 requested making protected paths configurable, not changing the default-provision mechanism. The PR body acknowledges this as "Scope increase."

  • [documentation-consistency] docs/review.md — The new REVIEW_PROTECTED_PATHS row in the Variables table has a significantly longer description than the existing REVIEW_FINDING_SEVERITY_THRESHOLD row, breaking visual consistency.
    Remediation: Move detailed parsing behavior to a separate paragraph below the table.

Previous run (3)

Review

Findings

Medium

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md, env/default-review-protected-paths.txt — PR modifies files under protected paths: env/, harness/, scripts/, skills/. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [fail-open] eval/scripts/run-fullsend.sh:226 — When the default protected paths file exists but contains only comments or blank lines, _defaults remains empty and emit_env "REVIEW_PROTECTED_PATHS" "" is called. post-review.sh interprets empty-but-set REVIEW_PROTECTED_PATHS as a deliberate opt-out, silently disabling protected-path enforcement in the eval context.
    Remediation: After the while loop, add a check: if [[ -z "${_defaults}" ]]; then echo "ERROR: default protected paths file yielded no entries" >&2; exit 1; fi

  • [GHA-workflow-command-injection] scripts/post-review.sh:198 — The ::error:: workflow command sanitizes the REVIEW_PROTECTED_PATHS value for newlines/CR and :: sequences, but does not strip %0A/%0D URL-encoded newlines, which GitHub Actions decodes in workflow command parameters. Exploitability is limited (requires CI config write access).
    Remediation: Add %0A and %0D stripping to the sanitization block.

  • [stale-hardcoded-list] agents/fix.md:85agents/fix.md contains a hardcoded protected paths list (lines 85–102) that is now stale. This PR moves the canonical list to env/default-review-protected-paths.txt. The new default list includes env/ which is absent from agents/fix.md. The list is advisory (enforcement lives in post-review.sh), so impact is limited to LLM guidance accuracy.
    Remediation: Update agents/fix.md to reference env/default-review-protected-paths.txt or update the list to match the new defaults.

  • [stale-reference] agents/code.md:76 — States "Protected paths are defined in post-review.sh." After this PR, protected paths are defined in env/default-review-protected-paths.txt (or via REVIEW_PROTECTED_PATHS env var), with post-review.sh as the enforcement point.
    Remediation: Update the reference to clarify the source of the protected paths definition.

Previous run (4)

Review

Findings

Medium

  • [fail-open] eval/scripts/run-fullsend.sh:229 — When the eval runner's default protected paths file is missing, it falls back to emit_env "REVIEW_PROTECTED_PATHS" "", which post-review.sh interprets as a deliberate opt-out, silently disabling protected-path enforcement. This converts a missing-config condition into permissive behavior rather than failing closed (as post-review.sh does at its own missing-file guard, lines 214–216).
    Remediation: When the defaults file is missing in run-fullsend.sh, either fail the eval run (exit 1) or leave REVIEW_PROTECTED_PATHS unset so post-review.sh's own missing-file guard can fire.

  • [stale-hardcoded-list] agents/fix.md:85 — agents/fix.md contains a hardcoded protected paths list (lines 85–102) that duplicates the old list formerly in post-review.sh. This PR moves the canonical list to env/default-review-protected-paths.txt, making the fix.md copy stale. The new default list includes env/ which is absent from agents/fix.md.
    Remediation: Replace the hardcoded list in agents/fix.md with a reference to the runtime-resolved protected paths list, or update the list to match the new defaults file including env/.

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md, env/default-review-protected-paths.txt — PR modifies files under protected paths: env/, harness/, scripts/, skills/. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [fail-open] scripts/post-review.sh:173 — The three-way resolution distinguishes between REVIEW_PROTECTED_PATHS being "set and empty" (disables enforcement) vs "unset" (reads defaults file). In harness/review.yaml, the variable is wired through as "${REVIEW_PROTECTED_PATHS}". If the fullsend templating engine resolves an unset outer variable to an empty string, the runner receives an empty value, triggering the disable path instead of the file-fallback path. The distinction between "unset" and "empty string" is fragile across template engines.

  • [GHA-workflow-command-injection] scripts/post-review.sh:198 — The ::error:: workflow command sanitizes the REVIEW_PROTECTED_PATHS value for newlines/CR and :: sequences, but does not strip %0A/%0D URL-encoded newlines, which GitHub Actions decodes in workflow command parameters. Exploitability is limited (requires CI config write access).
    Remediation: Add %0A and %0D stripping to the sanitization block.

  • [edge-case] scripts/post-review-test.sh:971 — The run_protected_paths_test helper exports REVIEW_PROTECTED_PATHS only when the protected_paths argument is non-empty. When it's empty (file-fallback tests), the subshell does not unset the variable. If the test runs in an environment where REVIEW_PROTECTED_PATHS is already set (e.g., CI), file-fallback tests silently exercise the wrong code path.
    Remediation: Add unset REVIEW_PROTECTED_PATHS in the else branch of the subshell.

  • [stale-reference] agents/code.md:76 — States "Protected paths are defined in post-review.sh." After this PR, protected paths are defined in env/default-review-protected-paths.txt (or via REVIEW_PROTECTED_PATHS env var), with post-review.sh as the enforcement point.


Labels: PR modifies review agent infrastructure (post-review.sh, harness config, skill definitions, eval runner)


Next steps:

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

Review

Findings

Medium

  • [runtime-mechanism] skills/pr-review/SKILL.md — SKILL.md instructs the sandbox agent to fall back to reading env/default-review-protected-paths.txt when REVIEW_PROTECTED_PATHS is not set or empty. This file is not mounted into the sandbox via host_files in harness/review.yaml, so the fallback cannot function inside the sandbox. While this path is currently unreachable in production (run-fullsend.sh always populates the env var, and review.yaml passes it via env.sandbox), the instruction describes a fallback that cannot work. Additionally, SKILL.md says "not set or empty" falls through to file reading, but post-review.sh (runner side) treats set-but-empty as disabling enforcement entirely — inconsistent semantics for the empty case.
    Remediation: Either mount env/default-review-protected-paths.txt into the sandbox via host_files, or remove the file-fallback instruction from SKILL.md and note that REVIEW_PROTECTED_PATHS is always provided by the harness. Align empty-string semantics between SKILL.md and post-review.sh.

  • [fail-open] scripts/post-review.sh — Setting REVIEW_PROTECTED_PATHS="" (explicitly empty) disables protected-path enforcement entirely. The harness YAML passes REVIEW_PROTECTED_PATHS: "${REVIEW_PROTECTED_PATHS}" — if the outer variable is unset, the fullsend templating engine may resolve this to an empty string, silently triggering the disable path. Only a ::notice:: annotation signals when protection is disabled.
    Remediation: Verify how the fullsend harness resolves ${REVIEW_PROTECTED_PATHS} when the outer variable is unset. Consider using a sentinel value (e.g., REVIEW_PROTECTED_PATHS=NONE) for the explicit-disable path instead of overloading empty string.

  • [stale-hardcoded-list] agents/fix.mdagents/fix.md contains a hardcoded protected paths list (lines ~85–102) that duplicates the old list formerly in post-review.sh. This PR moves the canonical list to env/default-review-protected-paths.txt, making the fix.md copy stale. The new default list includes env/ which is absent from agents/fix.md.
    Remediation: Replace the hardcoded list in agents/fix.md with a reference to the runtime-resolved protected paths list, or update the list to match the new defaults file including env/.

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md — PR modifies files under protected paths: harness/, scripts/, skills/. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [GHA-workflow-command-injection] scripts/post-review.sh — The ::error:: workflow command interpolates the raw REVIEW_PROTECTED_PATHS value unsanitized. While exploitability is limited (requires repo write access to CI configuration), applying the same sanitization pattern used for label actions (lines ~279–284) would be good defense-in-depth.

  • [edge-case] eval/scripts/run-fullsend.sh — When REVIEW_PROTECTED_PATHS is not set and the defaults file is missing, the eval runner emits REVIEW_PROTECTED_PATHS="". On the PR-head post-review.sh, a set-but-empty variable disables protected-path enforcement — the opposite of fail-closed intent in eval context.

  • [defense-in-depth] harness/review.yamlREVIEW_PROTECTED_PATHS is passed into the sandbox environment. This is now intentional and documented (SKILL.md step 3c-1 and step 6e reference the variable for governance-path resolution). The enforcement point (post-review.sh) runs independently on the runner and cannot be influenced by the sandbox agent.

  • [scope-creep-minor] env/default-review-protected-paths.txt — The defaults file adds env/ as a new protected path not present in the original hardcoded list. Defensible as self-protection of the new configuration mechanism.

  • [stale-reference] agents/code.md — States "Protected paths are defined in post-review.sh." After this PR, protected paths are defined in env/default-review-protected-paths.txt (or via REVIEW_PROTECTED_PATHS env var), with post-review.sh as the enforcement point.

  • [stale-reference] agents/fix.md — States "Protected-path enforcement lives in post-review.sh" without noting that the path list is now sourced externally from the env var or defaults file.

  • [consistency] harness/review.yaml — The env: block lists sandbox: before runner:, but the convention in other harness files (triage.yaml, fix.yaml, prioritize.yaml) is runner: first.

Previous run (6)

Review

Findings

Medium

  • [runtime-mechanism] skills/pr-review/SKILL.md — SKILL.md instructs the sandbox agent to fall back to reading env/default-review-protected-paths.txt when REVIEW_PROTECTED_PATHS is "not set or empty". This file is not mounted into the sandbox via host_files in harness/review.yaml. While this path is currently unreachable in production (run-fullsend.sh always populates the env var, and review.yaml passes it via env.sandbox), the instruction describes a fallback that cannot function inside the sandbox. Additionally, SKILL.md says "not set or empty" falls through to file reading, but post-review.sh (runner side) treats empty as a hard abort — the two components have inconsistent semantics for the same condition.
    Remediation: Either mount env/default-review-protected-paths.txt into the sandbox via host_files (making the fallback functional), or update SKILL.md to note that the env var is always pre-populated by the harness and the file-read fallback is not available in the sandbox.

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md — PR modifies files under protected paths: harness/, scripts/, skills/. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [edge-case] eval/scripts/run-fullsend.sh:434 — When REVIEW_PROTECTED_PATHS is not set and the defaults file is missing, the script emits an empty string. post-review.sh then aborts with "set but empty after parsing" — fail-closed but the error message obscures the real cause (missing defaults file).

  • [logic-error] scripts/post-review.sh — The guard if [[ ${#PROTECTED_PATHS[@]} -gt 0 ]] is always true at this point. All preceding branches either populate a non-empty array or abort with exit 1. Dead code that adds unnecessary nesting.

  • [defense-in-depth] harness/review.yamlREVIEW_PROTECTED_PATHS is passed into the sandbox environment. This is now intentional and documented (SKILL.md step 3c-1 and step 6e reference the variable for governance-path resolution). The enforcement point (post-review.sh) runs independently on the runner and cannot be influenced by the sandbox agent.

  • [scope-creep-minor] env/default-review-protected-paths.txt — The defaults file adds env/ as a new protected path not present in the original hardcoded list. Defensible as self-protection of the new configuration mechanism.

Previous run (7)

Review

Findings

Medium

  • [logic-error] docs/review.md:82 — The documentation claims "When unset or empty, defaults are read from env/default-review-protected-paths.txt. Setting to an empty string is treated the same as unset (fail-closed)." This is incorrect. In post-review.sh, when REVIEW_PROTECTED_PATHS is set to an empty string, the code enters the ${REVIEW_PROTECTED_PATHS+set} branch, parses zero entries, and aborts with exit 1. It does NOT fall through to read the defaults file. "Unset" (reads from file) and "empty string" (aborts) behave differently, contradicting the documentation.
    Remediation: Change the documentation to: "When unset, defaults are read from env/default-review-protected-paths.txt. When set to an empty string (or a value that parses to no valid paths), the script aborts (fail-closed)."

  • [runtime-mechanism] skills/pr-review/SKILL.md — SKILL.md instructs the agent in two places (step 3c-1 step 2 and step 6e Protected paths) to fall back to reading env/default-review-protected-paths.txt when REVIEW_PROTECTED_PATHS is not set. This file exists in the agents repo but is not mounted into the sandbox via host_files in harness/review.yaml. The sandbox contains the target repository, not the agents repo. Currently masked because run-fullsend.sh always populates the env var, but the fallback path described in SKILL.md is non-functional in the sandbox.
    Remediation: Either mount the file via host_files in review.yaml, or remove the file-fallback instruction from SKILL.md (the env var will always be provided by the harness).

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md, env/default-review-protected-paths.txt — PR modifies files under protected paths: harness/, scripts/, skills/, env/. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [defense-in-depth] harness/review.yamlREVIEW_PROTECTED_PATHS is passed into the sandbox environment. This is now intentional and documented (SKILL.md step 3c-1 and step 6e reference the variable for governance-path resolution). The enforcement point (post-review.sh) runs independently on the runner and cannot be influenced by the sandbox agent.

  • [edge-case] eval/scripts/run-fullsend.sh:456 — When REVIEW_PROTECTED_PATHS is not set and the defaults file is missing, the script emits an empty string. post-review.sh then aborts with "set but empty after parsing" — fail-closed but the error message obscures the real cause (missing defaults file).

  • [logic-error] scripts/post-review.sh:792 — The guard if [[ ${#PROTECTED_PATHS[@]} -gt 0 ]] is always true at this point. All preceding branches either populate a non-empty array or abort with exit 1. Dead code that adds unnecessary nesting.

  • [scope-creep-minor] env/default-review-protected-paths.txt — The defaults file adds env/ as a new protected path not present in the original hardcoded list. Defensible as self-protection of the new configuration mechanism.

  • [external-dependency] env/default-review-protected-paths.txt — New external configuration file creates a deployment dependency. Downstream repos that fork the review harness must include this file or explicitly set REVIEW_PROTECTED_PATHS. post-review.sh aborts (fail-closed) if neither exists.

Previous run (8)

Review

Findings

High

  • [fail-open] scripts/post-review.sh:173 — Setting REVIEW_PROTECTED_PATHS to an empty string explicitly disables all protected-path checking (PROTECTED_PATHS=()), allowing the review agent to approve PRs that touch governance files without downgrading to comment. This removes the "sole enforcement point" for protected-path governance. While this is a deliberate design choice (the code comment says "Explicitly empty — protection disabled"), empty-string assignment can occur through CI misconfiguration (e.g., REVIEW_PROTECTED_PATHS= with no value), and the disable happens silently with no log output. Issue Review agent: make protected paths configurable via environment variable #568 did not authorize a "disable all protection" mode — it requested "override or extend."
    Remediation: Remove the empty-string disable code path. If disabling protection is a legitimate need, require a distinct opt-out mechanism (e.g., REVIEW_PROTECTED_PATHS_DISABLE=true) that cannot be triggered by accidental empty-string assignment, and log when protection is disabled.

Medium

  • [runtime-mechanism] skills/pr-review/SKILL.md — SKILL.md instructs the LLM agent to check if REVIEW_PROTECTED_PATHS "is set" and use it for governance path resolution. However, eval/scripts/run-fullsend.sh unconditionally emits REVIEW_PROTECTED_PATHS as an empty string when unset (${REVIEW_PROTECTED_PATHS:-}), and review.yaml passes ${REVIEW_PROTECTED_PATHS} to the sandbox. The env var is always defined (as an empty string) in the eval environment. An LLM following the instruction may interpret an empty-but-defined env var as "set", split the empty string, and proceed with zero governance paths — affecting both the security-triage spawn prompt (Part 2 will have an empty governance paths list) and the step 6e protected-paths check. The post-review.sh enforcement correctly distinguishes set-but-empty from unset, so this is a defense-in-depth gap rather than an enforcement bypass.
    Remediation: Change SKILL.md to say "if set and non-empty" to match the semantics in post-review.sh, or have run-fullsend.sh only emit REVIEW_PROTECTED_PATHS when the caller explicitly provides a value.

  • [runtime-mechanism] skills/pr-review/SKILL.md — Both the triage procedure (step 2) and the protected-paths section (step 6e) instruct the agent to read env/default-review-protected-paths.txt as fallback when the env var is not set. This file exists in the agents repo but is not mounted into the sandbox via host_files in harness/review.yaml. The sandbox contains the target repository being reviewed, not the agents repo, so the file will not exist at the expected path. Currently masked by the always-set env var, but represents a latent bug that would surface if the empty-string issue is resolved.
    Remediation: Either mount env/default-review-protected-paths.txt into the sandbox via host_files in harness/review.yaml, or have the pre-script inject the file contents into the env var when unset.

  • [fail-open] scripts/post-review.sh — When the defaults file (env/default-review-protected-paths.txt) contains only blank lines and comments (all lines filtered out), the resulting PROTECTED_PATHS array is empty. The code reaches if [[ ${#PROTECTED_PATHS[@]} -gt 0 ]] and skips the entire protected-path check — silently disabling protection. This is inconsistent with the fail-closed behavior for the env var path, which explicitly aborts when parsing yields zero entries.
    Remediation: After reading the defaults file, add the same zero-length check: if [[ ${#PROTECTED_PATHS[@]} -eq 0 ]]; then echo "::error::..." >&2; exit 1; fi.

  • [fail-open] env/default-review-protected-paths.txt — The default protected paths list does not include env/ itself. A PR modifying env/default-review-protected-paths.txt (e.g., removing entries to weaken protection) would not be flagged as touching a protected path on the next run. Note: this is a pre-existing gap — the old hardcoded list also did not include env/.
    Remediation: Add env/ to the default protected paths list.

  • [privilege-escalation] harness/review.yamlREVIEW_PROTECTED_PATHS is passed into the sandbox environment where the review agent runs. While the agent needs to know protected paths for its own findings (SKILL.md step 6e), exposing the mutable configuration to the sandbox is a defense-in-depth concern. The base-branch SKILL.md already hardcodes the list, so no new information asymmetry is created, but making the list operator-configurable inside the sandbox widens the surface.
    Remediation: Consider whether the sandbox truly needs this variable. If the agent's protected-path findings are defense-in-depth (with post-review.sh as the sole enforcement point), the SKILL.md could embed defaults and the env var could be limited to env.runner.

  • [protected-path] harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md — PR modifies files under protected paths: harness/, scripts/, skills/. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [edge-case] harness/review.yamlreview.yaml now references ${REVIEW_PROTECTED_PATHS} and ${REVIEW_FINDING_SEVERITY_THRESHOLD} in both sandbox and runner env sections. The production CI workflow (in fullsend-ai/fullsend) must emit these env vars before merging this PR, or fullsend may fail to resolve the variable references at startup.

  • [logic-error] skills/pr-review/sub-agents/security-triage.md:40 — The hardcoded governance path list (16 concrete paths) was removed from the security-triage sub-agent definition and replaced with a reference to the orchestrator-provided "Active governance paths" list. The sub-agent now has zero governance path patterns of its own, making it entirely dependent on the orchestrator correctly resolving and injecting the paths via the spawn prompt (SKILL.md step 3c-1). If the orchestrator fails to inject them (e.g., due to the env var ambiguity above), the sub-agent has no fallback.

  • [scope-authorization-interpretation] scripts/post-review.sh — Issue Review agent: make protected paths configurable via environment variable #568 contains contradictory guidance: the body says "override or extend" (suggesting append capability), but the recommended approach says "replaces the default list entirely" (override-only). The implementation follows the recommended approach.

  • [breaking-schema] harness/review.yaml:48 — The runner_env key under forge.github was changed to env with sandbox and runner sub-keys. Three other harness files on main already use the new env format (fix.yaml, triage.yaml, prioritize.yaml), confirming the fullsend CLI parser supports both schemas.


Next steps:

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

Review

Findings

Medium

  • [runtime-mechanism] skills/pr-review/SKILL.md — SKILL.md instructs the LLM agent to check if REVIEW_PROTECTED_PATHS "is set" and use it for governance path resolution. However, run-fullsend.sh unconditionally emits REVIEW_PROTECTED_PATHS as an empty string when unset (${REVIEW_PROTECTED_PATHS:-}), and review.yaml passes ${REVIEW_PROTECTED_PATHS} to the sandbox. The env var is always defined (as an empty string) in the sandbox. An LLM following the natural-language instruction may interpret an empty-but-defined env var as "set", split the empty string, and proceed with zero governance paths — affecting both the security-triage spawn prompt (Part 2 will have an empty governance paths list) and the step 6e protected-paths check. The post-review.sh enforcement (the "sole enforcement point" per the code comment) handles empty strings correctly with its -n test and fail-closed guard, so this is a defense-in-depth gap rather than an enforcement bypass.
    Remediation: Change SKILL.md to say "if set and non-empty" to match the -n semantics in post-review.sh, or have run-fullsend.sh populate REVIEW_PROTECTED_PATHS with the contents of env/default-review-protected-paths.txt when the caller does not provide a value.

  • [runtime-mechanism] skills/pr-review/SKILL.md — Both the triage procedure (new step 2) and the protected-paths section (step 6e) instruct the agent to read env/default-review-protected-paths.txt as fallback when the env var is not set. This file is added to the repo by this PR but is not mounted into the sandbox via host_files in harness/review.yaml. The sandbox contains the target repository, not the agents repo, so the file will not exist at the expected path. Currently masked by the always-set env var (finding above), but represents a latent bug that would surface if the empty-string issue is resolved by not emitting the variable.
    Remediation: Either mount env/default-review-protected-paths.txt into the sandbox via host_files in harness/review.yaml, or ensure the default paths are always passed via the env var so the file-reading fallback is never needed.

  • [protected-path] harness/review.yaml — PR modifies files under protected paths: harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [breaking-schema] harness/review.yaml:48 — The runner_env key under forge.github was changed to env with sandbox and runner sub-keys. Three other harness files on main already use the new env format (fix.yaml, triage.yaml, prioritize.yaml), confirming the fullsend CLI parser supports both schemas. This aligns review.yaml with the newer convention rather than introducing a breaking change.

  • [scope-authorization-interpretation] scripts/post-review.sh — Issue Review agent: make protected paths configurable via environment variable #568 contains contradictory guidance: the body says "override or extend" (suggesting append capability), but the recommended approach says "replaces the default list entirely" (override-only). The implementation follows the recommended approach. If repository owners expect append semantics based on the issue body's phrasing, they may be surprised.

  • [variable-naming] scripts/post-review.sh:170PROTECTED_PATHS uses a generic name without namespace qualifier. The codebase convention in this file (REVIEW_CONTROL_LABELS at line 238) is to prefix global variables with REVIEW_. The old REVIEW_PROTECTED_PATHS array followed this pattern; the new internal array does not.

  • [edge-case] harness/review.yamlreview.yaml now references ${REVIEW_PROTECTED_PATHS} and ${REVIEW_FINDING_SEVERITY_THRESHOLD} in both sandbox and runner env sections. The production CI workflow (in fullsend-ai/fullsend) must emit these env vars before merging this PR, or fullsend may fail to resolve the variable references at startup.

Previous run (10)

Review

Findings

Medium

  • [runtime-mechanism] skills/pr-review/SKILL.md — SKILL.md instructs the LLM agent to check if REVIEW_PROTECTED_PATHS "is set" and use it for governance path resolution. However, run-fullsend.sh unconditionally emits REVIEW_PROTECTED_PATHS as an empty string when unset (${REVIEW_PROTECTED_PATHS:-}), and review.yaml passes ${REVIEW_PROTECTED_PATHS} to the sandbox. The env var is always defined (as an empty string) in the sandbox. An LLM following the natural-language instruction may interpret an empty-but-defined env var as "set", split the empty string, and proceed with zero governance paths — affecting both the security-triage spawn prompt (Part 2 will have an empty governance paths list) and the step 6e protected-paths check. The post-review.sh enforcement (the "sole enforcement point" per the code comment) handles empty strings correctly with its -n test and fail-closed guard, so this is a defense-in-depth gap rather than an enforcement bypass.
    Remediation: Change SKILL.md to say "if set and non-empty" to match the -n semantics in post-review.sh, or have run-fullsend.sh populate REVIEW_PROTECTED_PATHS with the contents of env/default-review-protected-paths.txt when the caller does not provide a value.

  • [runtime-mechanism] skills/pr-review/SKILL.md — Both the triage procedure (new step 2) and the protected-paths section (step 6e) instruct the agent to read env/default-review-protected-paths.txt as fallback when the env var is not set. This file is added to the repo by this PR but is not mounted into the sandbox via host_files in harness/review.yaml. The sandbox contains the target repository, not the agents repo, so the file will not exist at the expected path. Currently masked by the always-set env var (finding above), but represents a latent bug that would surface if the empty-string issue is resolved by not emitting the variable.
    Remediation: Either mount env/default-review-protected-paths.txt into the sandbox via host_files in harness/review.yaml, or ensure the default paths are always passed via the env var so the file-reading fallback is never needed.

  • [protected-path] harness/review.yaml — PR modifies files under protected paths: harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [breaking-schema] harness/review.yaml:48 — The runner_env key under forge.github was changed to env with sandbox and runner sub-keys. Three other harness files on main already use the new env format (fix.yaml, triage.yaml, prioritize.yaml), confirming the fullsend CLI parser supports both schemas. Two others (code.yaml, retro.yaml) still use runner_env. This aligns review.yaml with the newer convention rather than introducing a breaking change.

  • [scope-authorization-interpretation] scripts/post-review.sh — Issue Review agent: make protected paths configurable via environment variable #568 contains contradictory guidance: the body says "override or extend" (suggesting append capability), but the recommended approach says "replaces the default list entirely" (override-only). The implementation follows the recommended approach. If repository owners expect append semantics based on the issue body's phrasing, they may be surprised.

  • [variable-naming] scripts/post-review.sh:170PROTECTED_PATHS uses a generic name without namespace qualifier. The codebase convention in this file (REVIEW_CONTROL_LABELS at line 238) is to prefix global variables with REVIEW_. The old REVIEW_PROTECTED_PATHS array followed this pattern; the new internal array does not.

  • [edge-case] harness/review.yamlreview.yaml now references ${REVIEW_PROTECTED_PATHS} and ${REVIEW_FINDING_SEVERITY_THRESHOLD} in both sandbox and runner env sections. The production CI workflow (in fullsend-ai/fullsend) must emit these env vars before merging this PR, or fullsend may fail to resolve the variable references at startup.

Previous run (11)

Review

Findings

High

  • [runtime-mechanism] skills/pr-review/sub-agents/security-triage.md:40 — The hardcoded governance path list (16 concrete paths like .claude/**, .github/**, scripts/**, etc.) was removed and replaced with a reference to the REVIEW_PROTECTED_PATHS environment variable. The security-triage sub-agent is a Haiku model spawned with a composed prompt — it has no mechanism to read environment variables at runtime. When REVIEW_PROTECTED_PATHS is not set (the default case), the sub-agent receives zero concrete governance paths for classification, only seeing “Any path listed in the REVIEW_PROTECTED_PATHS environment variable is a governance or infrastructure path.” This degrades the triage classifier’s ability to identify governance files as security-critical in large PRs. See also: [security-control-weakening] finding at this location.
    Remediation: Either (a) keep the hardcoded governance path list in security-triage.md as the default classification criteria, or (b) update the orchestrator’s spawn prompt composition (SKILL.md step 3c-1) to resolve the protected paths list and include it in the triage prompt.

  • [breaking-schema] harness/review.yaml:48 — The runner_env key under forge.github was renamed to env with sandbox and runner sub-keys. The fullsend harness (in fullsend-ai/fullsend) consumes this YAML. The repo has mixed migration state: code.yaml and retro.yaml still use runner_env, while fix.yaml, triage.yaml, and prioritize.yaml already use the new env structure. If the fullsend CLI requires a minimum version for the new schema, this should be documented.
    Remediation: Verify whether the fullsend harness parser supports both schemas. If only the new schema is supported, update code.yaml and retro.yaml in the same PR for consistency. If this is a breaking change for older CLI versions, mark the commit with ! suffix per conventional commits.

Medium

  • [fail-open] scripts/post-review.sh:189 — After parsing REVIEW_PROTECTED_PATHS (env var or file), no check verifies that the resulting PROTECTED_PATHS array is non-empty. If the env var is set to a degenerate value that trims to nothing (e.g., only commas or whitespace), the array is empty and the protected-path enforcement loop becomes a no-op — an approve action for a PR touching sensitive paths would never be downgraded. The else branch correctly aborts when neither source is available, but the env-var and file-reading branches can produce empty arrays without aborting.

  • [runtime-mechanism] skills/pr-review/SKILL.md — The updated SKILL.md instructs the review agent: “If the variable is not set, read the default list from env/default-review-protected-paths.txt.” However, the review agent runs in a sandbox against the target repository, not the fullsend-ai/agents repo. The file env/default-review-protected-paths.txt exists only in the agents repo and is inaccessible from the sandbox. This instruction is unimplementable in the default case. Post-review.sh enforcement on the runner handles this correctly, but the agent cannot emit protected-path findings without knowing the path list.

  • [security-control-weakening] skills/pr-review/sub-agents/security-triage.md:40 — Same code location as the [runtime-mechanism] finding above, evaluated from the security dimension. Removing the hardcoded governance path list from the triage classifier weakens a security control: governance files (.github/, scripts/, CODEOWNERS, etc.) may no longer be classified as security-critical in large PRs, causing them to receive standard rather than prioritized review attention.

  • [protected-path] harness/review.yaml — PR modifies files under protected paths: harness/review.yaml, scripts/post-review.sh, scripts/post-review-test.sh, skills/pr-review/SKILL.md, skills/pr-review/sub-agents/security-triage.md. These are governance and infrastructure files that require human approval. PR links to issue Review agent: make protected paths configurable via environment variable #568 and provides context for the changes. Human approval is always required for protected-path changes regardless of context.

Low

  • [edge-case] scripts/post-review.sh — When REVIEW_PROTECTED_PATHS is not set in CI, the harness passes an empty string to the sandbox. The agent may interpret a set-but-empty env var as “no protected paths” and skip emitting protected-path findings. Post-review.sh handles this correctly (empty string is falsy for -n), but the agent-side behavior is ambiguous.

  • [scope-authorization-interpretation] scripts/post-review.sh — Issue Review agent: make protected paths configurable via environment variable #568 authorizes “override or extend” semantics, but the implementation provides override-only. When the env var is set, it fully replaces the defaults with no mechanism to append.

  • [variable-naming] scripts/post-review.sh — The internal array PROTECTED_PATHS uses a generic name. The codebase convention is namespace-qualified names (e.g., REVIEW_CONTROL_LABELS). Consider REVIEW_ACTIVE_PROTECTED_PATHS or similar.

  • [stale-reference] docs/code.md:44 — References runner_env in harness/code.yaml. While code.yaml still uses runner_env, this reference will become stale when the migration completes.

  • [configuration-file-location] scripts/post-review.sh — The ../env/ relative path pattern for the default file is a new convention in the codebase. Other scripts use absolute paths from env vars or paths within the same directory.


Labels: PR modifies review agent infrastructure (post-review.sh, SKILL.md, security-triage.md, harness config)


Next steps:

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

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🤖 Finished Fix · ❌ Failure · Started 6:10 PM UTC · Completed 6:19 PM UTC
Commit: bcf097f · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 6:11 PM UTC · Ended 6:21 PM UTC
Commit: edf335f · View workflow run →

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

⚠️ Post-fix script failed — Push rejected (exit code 1)

The fix agent completed, but the post-fix script failed before finishing.

Workflow run: https://github.com/fullsend-ai/.fullsend/actions/runs/30569103309

Details:
To https://github.com/fullsend-ai/agents.git
! [rejected] agent/568-configurable-protected-paths -> agent/568-configurable-protected-paths (fetch first)
error: failed to push some refs to 'https://github.com/fullsend-ai/agents.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
To https://github.com/fullsend-ai/agents.git
! [rejected] agent/568-configurable-protected-paths -> agent/568-configurable-protected-paths (stale info)
error: failed to push some refs to 'https://github.com/fullsend-ai/agents.git'
Please check the workflow logs for full details and retry with /fs-fix if appropriate.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 6:23 PM UTC · Ended 6:32 PM UTC
Commit: f3398a4 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 6:33 PM UTC · Ended 6:45 PM UTC
Commit: e961179 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 6:47 PM UTC · Ended 7:01 PM UTC
Commit: 1ceb993 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:03 PM UTC · Ended 7:20 PM UTC
Commit: d6c38e9 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:21 PM UTC · Completed 7:42 PM UTC
Commit: 2c33629 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review July 30, 2026 19:42

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 30, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review-only pass (no approve/request-changes). Two additional findings not yet covered by prior review threads or the automated review agent's summary on the current head commit.

Comment thread scripts/post-review.sh Outdated
Comment thread scripts/post-review.sh Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:09 PM UTC · Completed 9:30 PM UTC
Commit: 90f829f · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jul 30, 2026
@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🤖 Finished Fix · ✅ Success · Started 9:31 PM UTC · Completed 9:43 PM UTC
Commit: 90f829f · View workflow run →

fullsend-ai-coder Bot added a commit that referenced this pull request Jul 30, 2026
- Remove empty-string-disables-protection code path from post-review.sh;
  REVIEW_PROTECTED_PATHS="" now triggers fail-closed instead of silently
  disabling all protection (addresses High fail-open finding)
- Add zero-length check after reading defaults file to prevent silent
  protection bypass when file contains only comments/blanks
- Add env/ to default protected paths so the defaults file itself is
  protected from unreviewed modification
- Update SKILL.md to say "if set and non-empty" for REVIEW_PROTECTED_PATHS
  to prevent LLM misinterpretation of empty-but-defined env var
- Update eval runner to populate REVIEW_PROTECTED_PATHS with defaults from
  file when caller does not provide a value (avoids empty-string ambiguity)
- Update docs/review.md to document fail-closed semantics for empty string
- Replace explicit-empty-string-no-downgrade test with explicit-empty-string-aborts
- Add file-fallback-comments-only-aborts test for defaults file edge case

Addresses review feedback on #569
ralphbean added a commit that referenced this pull request Aug 3, 2026
- docs/review.md: REVIEW_PROTECTED_PATHS description said empty string
  is "treated the same as unset (fail-closed)" but unset reads defaults
  while empty string aborts — these are different behaviors. Clarify.
- eval case 003 annotation claimed to verify the direct file-read
  fallback branch in post-review.sh, but the eval harness always
  pre-populates REVIEW_PROTECTED_PATHS, so only the env-var branch runs.
  Correct the annotation to describe what's actually exercised.

Addresses review feedback on #569

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ralphbean added a commit that referenced this pull request Aug 3, 2026
…ECTED_PATHS

REVIEW_PROTECTED_PATHS="" now disables protected-path enforcement
entirely (deliberate operator opt-out), instead of failing closed.
A value that parses to zero entries after trimming (e.g. stray or
consecutive commas) still fails closed, since that's more likely a
misconfiguration than an intentional opt-out — the abort message now
includes the raw value to make that easier to diagnose.

Addresses review feedback on #569.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ralphbean added a commit that referenced this pull request Aug 3, 2026
… tests

Two review findings from PR #569:

- eval/scripts/run-fullsend.sh: when the default protected-paths file is
  missing, emit_env'ing REVIEW_PROTECTED_PATHS="" gets interpreted by
  post-review.sh as a deliberate opt-out (protection disabled) rather
  than a fail-closed error. Exit 1 instead, matching post-review.sh's
  own missing-file guard.

- scripts/post-review-test.sh: run_protected_paths_test only exported
  REVIEW_PROTECTED_PATHS when the test's protected_paths argument was
  non-empty, so file-fallback tests could silently inherit a stale
  value from the calling environment instead of exercising the
  defaults-file path. Unset it explicitly in the empty-argument case,
  with a new test that reproduces the leak.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:14 PM UTC · Completed 8:34 PM UTC
Commit: b6c0ad4 · View workflow run →

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread harness/review.yaml
REVIEW_PROTECTED_PATHS: ".claude/,.cursor/,.gitattributes,.github/,.pre-commit-config.yaml,AGENTS.md,agents/,api-servers/,CLAUDE.md,CODEOWNERS,Containerfile,Dockerfile,env/,harness/,images/,plugins/,policies/,scripts/,skills/"

timeout_minutes: 20

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] edge-case

The default REVIEW_PROTECTED_PATHS value adds env/ as a new protected path prefix that was NOT in the previous hardcoded list in post-review.sh. This is a behavioral change: any PR touching env/ files will now trigger a protected-path downgrade where it previously would not.

Suggested fix: If adding env/ is intentional, document the rationale explicitly. If unintentional, remove env/ from the default value in harness/review.yaml.

- `plugins/**` — plugin definitions
- `scripts/**` — pre/post scripts (CI and deployment)
- `skills/**` — skill definitions
The orchestrator provides an **Active governance paths** list in the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] runtime-mechanism

The hardcoded governance paths list was removed and replaced with a dependency on the orchestrator injecting an 'Active governance paths' section in the spawn prompt. If the orchestrator omits or malforms this section, the sub-agent silently loses all governance-path classification capability with no fallback.

Suggested fix: Add a defensive instruction with a minimal fallback list to use when no Active governance paths section is present.

Comment thread scripts/post-review.sh
for _entry in "${PROTECTED_PATHS[@]}"; do
_entry="$(echo "${_entry}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[[ -n "${_entry}" ]] && _trimmed+=("${_entry}")
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] GHA-workflow-command-injection

The ::error:: workflow command sanitizes the interpolated value using :: to : collapse, which is not idempotent (::: collapses to ::). The same file's REVIEW_FINDING_SEVERITY_THRESHOLD sanitization uses the more robust per-character stripping pattern. Exploitability is limited (requires CI config write access).

Suggested fix: Use per-character stripping (//%/ and //:/) matching the existing pattern in the same file.

Comment thread harness/review.yaml
# agent's own output and the post-script's defense-in-depth filter.
# Best practice is to keep them in sync. Repos needing different values
# override these keys via harness `base:` composition.
runner:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] fail-open-risk

Setting REVIEW_PROTECTED_PATHS to an empty string disables all protected-path enforcement with a ::notice:: log message. This is a deliberate design decision (explicit opt-out), not an accidental bypass.

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review sweep — 3 findings verified against PR head (b6c0ad4) and deduplicated against existing review threads. All three are new (not previously raised despite the PR's extensive review history). See inline comments for details.

Comment thread scripts/post-review.sh
_entry="$(echo "${_entry}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[[ -n "${_entry}" ]] && _trimmed+=("${_entry}")
done
PROTECTED_PATHS=("${_trimmed[@]}")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[HIGH] Empty-array expansion under set -u crashes on bash 3.2, masking the fail-closed error message

When REVIEW_PROTECTED_PATHS trims down to zero valid entries (e.g. ",,, ,"), this builds _trimmed=() in the loop above and then does PROTECTED_PATHS=("${_trimmed[@]}") before the ${#PROTECTED_PATHS[@]} -eq 0 check below. The script uses set -euo pipefail (line 22). Reproduced directly: /bin/bash -c 'set -u; a=(); b=("${a[@]}"); echo ok' fails with a[@]: unbound variable on bash 3.2.57 (the default /bin/bash on macOS, and still present on minimal/BusyBox-adjacent images). So expanding an empty _trimmed array crashes with a raw "unbound variable" error instead of ever reaching the intended ::error::...likely misconfigured... message a few lines down.

The degenerate-paths-aborts test in scripts/post-review-test.sh (around line 1173) exercises exactly this scenario (REVIEW_PROTECTED_PATHS=",,, ,") and greps stdout for "likely misconfigured" — that test would fail (wrong error text, though still non-zero exit) under bash <4.4.

Suggested fix: Guard the expansion, e.g. PROTECTED_PATHS=(); [[ ${#_trimmed[@]} -gt 0 ]] && PROTECTED_PATHS=("${_trimmed[@]}"), or build the array via a for-loop append that never dereferences a possibly-empty array's [@]. Consider adding a bash-3.2 (or similarly old) leg to the test matrix.

# harness/review.yaml (not a ${VAR} passthrough), so it needs no handling
# here — only REVIEW_FINDING_SEVERITY_THRESHOLD is a real caller-supplied var.
if [[ "$AGENT" == "review" ]]; then
emit_env "REVIEW_FINDING_SEVERITY_THRESHOLD" "${REVIEW_FINDING_SEVERITY_THRESHOLD:-}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] Stale/incorrect rationale for asymmetric REVIEW_FINDING_SEVERITY_THRESHOLD handling causes eval runs to fail-closed unnecessarily

The comment above (lines 205-207) states "REVIEW_PROTECTED_PATHS is a literal default baked into harness/review.yaml (not a ${VAR} passthrough), so it needs no handling here — only REVIEW_FINDING_SEVERITY_THRESHOLD is a real caller-supplied var." Diffing this PR's head against the merge-base shows the REVIEW_FINDING_SEVERITY_THRESHOLD: "low" lines under both env.runner and env.sandbox in harness/review.yaml are unchanged context — they already existed as literal defaults before this PR, exactly like REVIEW_PROTECTED_PATHS now does. The asymmetric treatment is based on a false premise.

Practical consequence: post-review.sh's severity-validation block does REVIEW_FINDING_SEVERITY_THRESHOLD="${REVIEW_FINDING_SEVERITY_THRESHOLD:-}" followed by a case statement whose *) catch-all treats an empty string as invalid and posts {"action":"failure","reason":"tool-failure"} before exiting 1. Since this line unconditionally emits ${REVIEW_FINDING_SEVERITY_THRESHOLD:-} (empty when the caller hasn't exported it), any review-agent eval run that doesn't pre-export this var — including this PR's own new 003-protected-path-downgrade case, whose input.yaml never sets it — will fail with a tool-failure unrelated to the protected-paths feature under test.

Suggested fix: Default explicitly to match harness/review.yaml's documented default, e.g. emit_env "REVIEW_FINDING_SEVERITY_THRESHOLD" "${REVIEW_FINDING_SEVERITY_THRESHOLD:-low}", and correct the now-inaccurate comment about which variable needs handling here.

Comment thread scripts/post-review.sh
fi
fi

if [[ ${#PROTECTED_PATHS[@]} -gt 0 ]]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] Disabling protected-path enforcement silently disables the unrelated "PR has no changed files" safety check

The pre-existing gh pr view --json files fetch and its PR_FILES empty-check ("Failed to fetch PR files or PR has no changed files — refusing to approve", a few lines below) is a defensive check unrelated to protected paths, but it's now wrapped entirely inside if [[ ${#PROTECTED_PATHS[@]} -gt 0 ]]; then. When an operator sets REVIEW_PROTECTED_PATHS="" to deliberately opt out of protected-path enforcement (a documented, supported use case per docs/review.md), they also silently lose this independent safety net that refuses to approve a PR whose file list couldn't be fetched or is empty.

Suggested fix: Keep the PR_FILES fetch and empty-check outside/independent of the ${#PROTECTED_PATHS[@]} -gt 0 guard so that safety net always applies to every approve action, and only gate the protected-path pattern-matching loop itself on a non-empty PROTECTED_PATHS.

ralphbean and others added 22 commits August 6, 2026 14:29
When the REVIEW_PROTECTED_PATHS environment variable is set (comma-
separated list of path prefixes), it replaces the built-in default list.
When unset, the existing defaults are preserved.

Changes:
- post-review.sh: parse REVIEW_PROTECTED_PATHS env var into the array
  used for protected-path matching, with whitespace trimming
- post-review-test.sh: add 5 integration tests covering default
  behavior, custom overrides, whitespace handling, and non-matches
- SKILL.md: document the override mechanism
- docs/review.md: add REVIEW_PROTECTED_PATHS to the Variables table

Closes #568

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Add env/default-review-protected-paths.txt as the single source of
truth for the default protected paths list. post-review.sh reads
REVIEW_PROTECTED_PATHS (comma-separated) when set, otherwise falls
back to the defaults file. If neither is available, it aborts.

Remove duplicated path lists from SKILL.md and security-triage.md —
both now reference the env var / defaults file instead.

Changes:
- env/default-review-protected-paths.txt: new canonical defaults file
- scripts/post-review.sh: env var → file fallback → abort
- scripts/post-review-test.sh: 8 tests covering env var override,
  file fallback, whitespace trimming, and missing-file abort
- skills/pr-review/SKILL.md: reference env var and defaults file
- skills/pr-review/sub-agents/security-triage.md: reference env var
- docs/review.md: add REVIEW_PROTECTED_PATHS to Variables table
- scripts/post-code.sh, post-fix.sh: update comments

Closes #568

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…rness

Add eval/review/cases/001-protected-path-downgrade — the first review
eval case. A PR pins actions/checkout to a full SHA in .github/workflows/
ci.yml (a default protected path). The review agent should approve the
change but post-review.sh must downgrade the approval because .github/
is protected. Expected outcome: requires-manual-review label, no
ready-for-merge.

Also restructure harness/review.yaml:
- Rename runner_env → env.runner
- Add env.sandbox with REVIEW_PROTECTED_PATHS and
  REVIEW_FINDING_SEVERITY_THRESHOLD so the agent can read them

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Reserve 001/002 for existing unmerged review eval cases.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
Empty entries from leading/trailing/consecutive commas in
REVIEW_PROTECTED_PATHS would match all files, causing every approval
to be downgraded. Filter them out after trimming whitespace.

Also rebuilds bundled scripts (post-code.sh, post-fix.sh).

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The setup-fixture hook pushes test content to an ephemeral GitHub repo.
Pushing .github/workflows/ files requires a token with `workflow` scope,
which the CI token does not have. Replace the workflow fixture with a
dependabot.yml file — still under .github/ so the protected-path
downgrade logic is exercised identically.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
- Add fail-closed guard: abort if PROTECTED_PATHS is empty after
  parsing a degenerate REVIEW_PROTECTED_PATHS value (e.g. ",,,").
- Add test for degenerate input.
- Fix security-triage sub-agent: the orchestrator now resolves
  governance paths and includes them in the spawn prompt (Part 2)
  instead of referencing REVIEW_PROTECTED_PATHS, which the Haiku
  sub-agent cannot read at runtime.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
fullsend validates that all ${VAR} references in harness YAML resolve
to set host variables. REVIEW_PROTECTED_PATHS and
REVIEW_FINDING_SEVERITY_THRESHOLD are optional (post-review.sh falls
back to defaults) but the harness references them unconditionally.
Emit empty defaults in run-fullsend.sh for the review agent so
fullsend's env validation passes.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The eval fixture had no linked issue, so the agent chose
request-changes for the protected-path finding. GitHub returns 422
when the same token that created the PR tries to submit a
request-changes review (self-review). Add a seed issue and link it
in the PR body so the agent has sufficient context to approve —
which post-review.sh then downgrades to comment.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
The review agent used 41 turns (partly due to model availability
retries). Bump the budget from 30 to 50 to accommodate variance.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
CI failed because the review agent cost $3.17, exceeding the $3.00
budget. Raise to $4.00 for headroom.

Assisted-by: Claude claude-opus-4-6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…only

Move protected-paths resolution and fail-closed guards inside the
approve conditional so non-approve actions (comment, reject,
request-changes) are not blocked by degenerate REVIEW_PROTECTED_PATHS
values or a missing defaults file.

Also distinguish set-but-empty REVIEW_PROTECTED_PATHS="" (disables
protection) from unset (falls through to defaults file). This lets
repo owners explicitly opt out of protected-path enforcement.

Signed-off-by: Ralph Bean <rbean@redhat.com>
Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
- Remove empty-string-disables-protection code path from post-review.sh;
  REVIEW_PROTECTED_PATHS="" now triggers fail-closed instead of silently
  disabling all protection (addresses High fail-open finding)
- Add zero-length check after reading defaults file to prevent silent
  protection bypass when file contains only comments/blanks
- Add env/ to default protected paths so the defaults file itself is
  protected from unreviewed modification
- Update SKILL.md to say "if set and non-empty" for REVIEW_PROTECTED_PATHS
  to prevent LLM misinterpretation of empty-but-defined env var
- Update eval runner to populate REVIEW_PROTECTED_PATHS with defaults from
  file when caller does not provide a value (avoids empty-string ambiguity)
- Update docs/review.md to document fail-closed semantics for empty string
- Replace explicit-empty-string-no-downgrade test with explicit-empty-string-aborts
- Add file-fallback-comments-only-aborts test for defaults file edge case

Addresses review feedback on #569
- docs/review.md: REVIEW_PROTECTED_PATHS description said empty string
  is "treated the same as unset (fail-closed)" but unset reads defaults
  while empty string aborts — these are different behaviors. Clarify.
- eval case 003 annotation claimed to verify the direct file-read
  fallback branch in post-review.sh, but the eval harness always
  pre-populates REVIEW_PROTECTED_PATHS, so only the env-var branch runs.
  Correct the annotation to describe what's actually exercised.

Addresses review feedback on #569

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…ECTED_PATHS

REVIEW_PROTECTED_PATHS="" now disables protected-path enforcement
entirely (deliberate operator opt-out), instead of failing closed.
A value that parses to zero entries after trimming (e.g. stray or
consecutive commas) still fails closed, since that's more likely a
misconfiguration than an intentional opt-out — the abort message now
includes the raw value to make that easier to diagnose.

Addresses review feedback on #569.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…tion

fullsend-ai-review pointed out the ::error:: workflow command
interpolates the raw REVIEW_PROTECTED_PATHS value unsanitized. Apply
the same newline/CR/:: stripping already used for label-actions
sanitization before it hits the log.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
waynesun09 noticed the three "missing/comments-only defaults file"
tests all copied post-review.sh into distinctly-named siblings of the
same TMPDIR, so SCRIPT_DIR/../env collapsed to the same path for all
three regardless of the leaf directory name — making them silently
order-dependent. Give each test its own case-<name>/{scripts,env}
parent so the path can't collide.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…KILL.md

waynesun09 found that the review agent's own instructions still said
"if not set or empty, use defaults" — contradicting post-review.sh's
current behavior of treating explicit-empty as a deliberate opt-out.
That meant the review agent would still emit a protected-path finding
(and the schema would still block approve) even when an operator had
opted out via REVIEW_PROTECTED_PATHS="". Spell out the three-way
resolution (set/non-empty, set/empty, unset) in both the "Protected
paths" section and the security-triage governance-paths step so they
match the script.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…HS in eval harness

waynesun09 flagged two issues in run-fullsend.sh's REVIEW_PROTECTED_PATHS
handling:

- `[[ -n "${REVIEW_PROTECTED_PATHS:-}" ]]` treated explicit-empty the
  same as unset, so the eval harness could never exercise the
  opt-out path post-review.sh now supports. Switch to the same
  `${VAR+set}` check post-review.sh uses.
- The defaults-file parsing used `sed | paste` without trimming
  per-entry whitespace, diverging from post-review.sh's trim-then-join
  logic. Match it so a future indented entry in the defaults file
  can't desync the two parsers.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
… tests

Two review findings from PR #569:

- eval/scripts/run-fullsend.sh: when the default protected-paths file is
  missing, emit_env'ing REVIEW_PROTECTED_PATHS="" gets interpreted by
  post-review.sh as a deliberate opt-out (protection disabled) rather
  than a fail-closed error. Exit 1 instead, matching post-review.sh's
  own missing-file guard.

- scripts/post-review-test.sh: run_protected_paths_test only exported
  REVIEW_PROTECTED_PATHS when the test's protected_paths argument was
  non-empty, so file-fallback tests could silently inherit a stale
  value from the calling environment instead of exercising the
  defaults-file path. Unset it explicitly in the empty-argument case,
  with a new test that reproduces the leak.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…ew.yaml

Replaces the separate env/default-review-protected-paths.txt file and its
three-way (set / set-empty / unset) resolution ladder — previously
duplicated across post-review.sh, eval/scripts/run-fullsend.sh, and
skills/pr-review/SKILL.md — with a single literal default declared
directly in harness/review.yaml. Repos needing a different list override
it via harness composition instead of an env var; unset is now a hard
misconfiguration error rather than a file-read fallback.

- harness/review.yaml: REVIEW_PROTECTED_PATHS is a literal default in
  both the runner and sandbox env stanzas, not a ${VAR} passthrough.
- post-review.sh: collapsed to two cases (non-empty / explicitly-empty);
  removed the defaults-file lookup entirely.
- run-fullsend.sh: removed the now-dead default-computation block.
- Deleted env/default-review-protected-paths.txt.
- Updated SKILL.md, docs/review.md, and the 003-protected-path-downgrade
  eval case's annotations to match.
- post-review-test.sh: removed the file-fallback tests, simplified the
  two "missing defaults" tests to plain unset-aborts tests, and exports
  a module-level REVIEW_PROTECTED_PATHS default so generic integration
  tests reflect that harness/review.yaml always sets it in production.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
…s tests

main's severity-threshold refactor (merged after this branch diverged)
made post-review.sh hard-fail when REVIEW_FINDING_SEVERITY_THRESHOLD is
unset or invalid, rather than silently defaulting to "low". The
protected-paths test helpers introduced here predate that change and
didn't export it, so rebasing onto main broke them.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@ralphbean
ralphbean force-pushed the agent/568-configurable-protected-paths branch from b6c0ad4 to bb20f15 Compare August 6, 2026 18:59

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review-only pass (no approve/request-changes). One finding below.

Comment thread harness/review.yaml
# Best practice is to keep them in sync. Repos needing different values
# override these keys via harness `base:` composition.
runner:
REVIEW_FINDING_SEVERITY_THRESHOLD: "low"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[MEDIUM] PR description's claim about newly wiring REVIEW_FINDING_SEVERITY_THRESHOLD is stale/false

The PR body currently states: "Wires REVIEW_FINDING_SEVERITY_THRESHOLD into harness/review.yaml's runner env. post-review.sh already read it on the runner side, but it was never plumbed through runner_env — only the sandbox got it via env/review.env. So runner-side severity filtering silently defaulted to low before this PR."

This is factually stale as of head bb20f15. The diff for harness/review.yaml shows runner: REVIEW_FINDING_SEVERITY_THRESHOLD: "low" and sandbox: REVIEW_FINDING_SEVERITY_THRESHOLD: "low" as unchanged context — the only lines this PR actually adds to this file are the two new REVIEW_PROTECTED_PATHS: ... entries. Git history shows the commit that originally added REVIEW_FINDING_SEVERITY_THRESHOLD to env.runner/env.sandbox is an ancestor of the merge-base between origin/main and this branch — i.e. that wiring already existed on main before this PR branched, contradicting the claim that this PR is the one doing the wiring. This looks like a holdover from an earlier iteration of the PR (before it was rebased onto a main that already contained that change) and from an earlier, now-resolved review thread where this explanation was added to the description in response to a since-superseded concern.

Suggestion: Correct the PR description to remove or rewrite the harness/review.yaml claim so it accurately reflects that this PR's only change to that file is adding REVIEW_PROTECTED_PATHS; REVIEW_FINDING_SEVERITY_THRESHOLD's runner-env wiring predates this branch and shouldn't be attributed to it.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review agent: make protected paths configurable via environment variable

2 participants