Skip to content

Strix 2 (1/4): scope governance foundation - #1395

Open
Pocs321 wants to merge 1 commit into
usestrix:mainfrom
Pocs321:strix2-foundation
Open

Pocs321 wants to merge 1 commit into
usestrix:mainfrom
Pocs321:strix2-foundation

Conversation

@Pocs321

@Pocs321 Pocs321 commented Oct 1, 2026

Copy link
Copy Markdown

1 of 4 — splits #1394 into per-area, dependency-ordered PRs. This is the base the others build on.

Adds the authorized-scope policy and enforcement, all additive and opt-in (a run with no scope.yaml is unchanged):

  • strix/scope/ — ScopePolicy schema (web / api / network / cloud) + a fail-closed loader + runtime enforcement (enforce_target / enforce_arguments / enforce_shell_command).
  • strix/tools/scope/ — check_scope / scope_status agent tools.
  • Scope check wired at the call_mcp boundary (tools/mcp/agent_tools.py), no-op without a policy.
  • --scope-config / --allow-intrusive CLI flags.
  • strix/strix2_ext.py — the single idempotent install hook (registers Strix 2 tools + loads the policy), called once from run_strix_scan (the one core edit).
  • CI (lint + mypy + Strix 2 tests, plus a non-blocking full baseline), design docs under docs/strix2/, THIRD_PARTY.md, and a scope.yaml template with no live targets.

Review / merge order

This series is dependency-ordered and meant to merge in order:

  1. this PR — scope foundation
  2. two-tier findings (candidate tier + annotations + SARIF tags) — needs this
  3. host-side read-only AWS MCP wrapper — needs this
  4. domain playbooks + efficiency controls + eval — needs this + [Question] Cost Estimates for Different LLMs #2

Until this merges, the later PRs' diffs include these foundation commits; they shrink automatically as this lands.

CI (lint + mypy + Strix 2 tests) is green on Python 3.12 and 3.13.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 0/5

[Medium risk] Adds scope authorization framework and CI pipeline.

This PR is not safe to merge until the scope checks cover the reachable tool paths and honor the operator's limits.

Findings

  1. P1 Example scope blocks scans ▶
  2. P1 Security Shell commands escape scope ▶
  3. P1 Security Proxy replay escapes scope ▶
  4. P1 Security Hostnames bypass MCP scope ▶
  5. P1 Security API path check can be escaped ▶
  6. P1 Security Intrusive MCP calls stay enabled ▶
  7. P1 URL contents block valid calls ▶
  8. P1 Security API calls skip port limits ▶
  9. P2 Bad ports break scope checks ▶
  10. P2 Failed starts keep log handlers ▶
  11. P2 CI actions can change underfoot ▶
  12. P2 Cloud wrapper listed too early ▶
Fix with agent prompt
### Issue 1
scope.yaml:1
A scan started from the repository root loads the checked-in `scope.yaml` automatically. Even if the operator did not choose a scope file, this example policy rejects targets outside its sample entries. Make the template opt-in rather than active by default.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
strix/scope/enforcement.py:165
`enforce_shell_command` is never called by the shell tool. With a policy loaded, an agent can run `nmap 8.8.8.8` or `curl https://outside.example` through `exec_command` without a scope check. Wire the check into the shell boundary before dispatch.

**How this was verified:** The shell wrapper dispatches commands without calling `enforce_shell_command`.

### Issue 3
strix/tools/mcp/agent_tools.py:294
The new policy check covers `call_mcp`, but not `repeat_request`. An agent can set `modifications["url"]` to an unauthorized host, and the proxy sends the request without checking the loaded policy. Check the final replay URL before sending.

**How this was verified:** `repeat_request` builds and sends the modified URL without calling a scope guard.

### Issue 4
strix/scope/enforcement.py:37-44
`extract_targets` skips bare hostnames. An MCP call such as `{"host": "outside.example"}` therefore passes `_scope_denial` without a decision. No domain wrapper in this PR checks the argument later. Check known target fields before dispatch instead of treating an empty extraction as approval.

**How this was verified:** The extractor ignores bare hostnames, and `call_mcp` dispatches when it returns no denial.

### Issue 5
strix/scope/schema.py:320-325
`_url_prefix_matches` compares the raw URL without resolving `..` path segments. With only `https://api.example.com/v1` authorized, it accepts `https://api.example.com/v1/../admin` because the rest starts with `/`, even though the request can reach `/admin`. Compare the resolved request path with the authorized base.

**How this was verified:** The matcher accepts the raw `/../admin` suffix, and the MCP boundary forwards the original arguments.

### Issue 6
strix/tools/mcp/agent_tools.py:56
`call_mcp` calls `enforce_arguments` with the default `intrusive=False`. A configured MCP write or delete tool aimed at an in-scope target therefore passes even when `allow_intrusive` is false. Identify state-changing tools and apply the gate before dispatch.

**How this was verified:** The MCP call uses the non-intrusive default, which skips the only `allow_intrusive` check.

### Issue 7
strix/scope/enforcement.py:103-112
`extract_targets` treats numbers inside a URL as separate destinations. For an authorized `https://example.com/report/123456789012` call, it approves the URL but then treats the path segment as an AWS account and refuses it unless that account is separately authorized. Extract destinations from the URL's host, not its path or query.

### Issue 8
strix/scope/schema.py:220-223
The API-base branch returns approval before checking `network.ports`. If an operator authorizes `https://api.example.com:8443/v1` as an API base but allows only port 443, a call to port 8443 passes. Apply the configured port limit before approving this branch.

**How this was verified:** An API prefix match returns immediately; only the later branches check the port.

### Issue 9
strix/scope/schema.py:225-227
`_check_url` reads `parsed.port` without catching `ValueError`. If an agent checks `http://example.com:abc`, it gets a tool error instead of a clear scope denial. Return a denied `ScopeDecision` for an invalid port.

### Issue 10
strix/core/runner.py:231
`install_strix2_extensions` can raise for a malformed scope file after scan logging starts but before the runner's cleanup block begins. The run leaves its log handlers attached and its file open. Load the policy before opening scan resources, or cover this call with cleanup.

### Issue 11
.github/workflows/ci.yml:36-37
The new CI workflow uses mutable action tags, while the existing release workflow pins those actions to commit hashes. A moved tag could change what code CI runs without a repository change. Pin checkout and setup actions here too.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 12
THIRD_PARTY.md:61-65
`THIRD_PARTY.md` lists `strix/mcp_servers/aws.py` as landed and scope-gated, but this PR contains no `strix/mcp_servers` package. Mark the wrapper as planned until it lands so readers do not rely on a cloud guard that is not present.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR adds an optional authorized-scope policy to Strix and checks MCP calls against it when a policy is loaded. It also adds scope tools and CLI controls, plus CI and design documentation.

  • Defines scope for web, API, network, and cloud targets, with intrusive actions off by default.
  • Loads the policy at scan startup and uses it to answer agent checks and refuse out-of-scope MCP calls.
  • Adds a CI workflow and documents Strix 2’s design and extension points.

Reviews (1) · Last reviewed commit: "Strix 2 (1/4): scope governance foundati..."

Comment thread scope.yaml
@@ -0,0 +1,57 @@
# Strix 2 — Authorized Scope Policy (TEMPLATE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Example scope blocks scans

A scan started from the repository root loads the checked-in scope.yaml automatically. Even if the operator did not choose a scope file, this example policy rejects targets outside its sample entries. Make the template opt-in rather than active by default.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scope.yaml
Line: 1

Comment:
**Example scope blocks scans**

A scan started from the repository root loads the checked-in `scope.yaml` automatically. Even if the operator did not choose a scope file, this example policy rejects targets outside its sample entries. Make the template opt-in rather than active by default.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

)


def enforce_shell_command(command: str) -> ScopeDecision | None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Shell commands escape scope

enforce_shell_command is never called by the shell tool. With a policy loaded, an agent can run nmap 8.8.8.8 or curl https://outside.example through exec_command without a scope check. Wire the check into the shell boundary before dispatch.

How this was verified: The shell wrapper dispatches commands without calling enforce_shell_command.

Knowledge Base Used: Agent tools and workflows

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/scope/enforcement.py
Line: 165

Comment:
**Shell commands escape scope**

`enforce_shell_command` is never called by the shell tool. With a policy loaded, an agent can run `nmap 8.8.8.8` or `curl https://outside.example` through `exec_command` without a scope check. Wire the check into the shell boundary before dispatch.

**How this was verified:** The shell wrapper dispatches commands without calling `enforce_shell_command`.

**Knowledge Base Used:** [Agent tools and workflows](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/agent-tools-and-workflows.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

return invalid_arguments
if arguments is not None and not isinstance(arguments, dict):
return invalid_arguments
denial = _scope_denial(arguments)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Proxy replay escapes scope

The new policy check covers call_mcp, but not repeat_request. An agent can set modifications["url"] to an unauthorized host, and the proxy sends the request without checking the loaded policy. Check the final replay URL before sending.

How this was verified: repeat_request builds and sends the modified URL without calling a scope guard.

Knowledge Base Used: Agent tools and workflows

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/tools/mcp/agent_tools.py
Line: 294

Comment:
**Proxy replay escapes scope**

The new policy check covers `call_mcp`, but not `repeat_request`. An agent can set `modifications["url"]` to an unauthorized host, and the proxy sends the request without checking the loaded policy. Check the final replay URL before sending.

**How this was verified:** `repeat_request` builds and sends the modified URL without calling a scope guard.

**Knowledge Base Used:** [Agent tools and workflows](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/agent-tools-and-workflows.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +37 to +44
# High-confidence target patterns only, so a scope check never rejects a legit
# MCP call over an ambiguous string. Bare hostnames are intentionally excluded
# here (too many false positives); domain tools check those against their known
# target argument directly.
_URL_RE = re.compile(r"\b[a-z][a-z0-9+.\-]*://[^\s\"'<>]+", re.IGNORECASE)
_ARN_RE = re.compile(r"\barn:aws:[^\s\"'<>]+", re.IGNORECASE)
_ACCOUNT_RE = re.compile(r"\b\d{12}\b")
_IPV4_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Hostnames bypass MCP scope

extract_targets skips bare hostnames. An MCP call such as {"host": "outside.example"} therefore passes _scope_denial without a decision. No domain wrapper in this PR checks the argument later. Check known target fields before dispatch instead of treating an empty extraction as approval.

How this was verified: The extractor ignores bare hostnames, and call_mcp dispatches when it returns no denial.

Knowledge Base Used: MCP integration

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/scope/enforcement.py
Line: 37-44

Comment:
**Hostnames bypass MCP scope**

`extract_targets` skips bare hostnames. An MCP call such as `{"host": "outside.example"}` therefore passes `_scope_denial` without a decision. No domain wrapper in this PR checks the argument later. Check known target fields before dispatch instead of treating an empty extraction as approval.

**How this was verified:** The extractor ignores bare hostnames, and `call_mcp` dispatches when it returns no denial.

**Knowledge Base Used:** [MCP integration](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/mcp-integration.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix/scope/schema.py
Comment on lines +320 to +325
normalized_target = norm(target)
normalized_base = norm(base)
if not normalized_target.startswith(normalized_base):
return False
remainder = normalized_target[len(normalized_base) :]
return remainder == "" or remainder[0] in "/?#"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security API path check can be escaped

_url_prefix_matches compares the raw URL without resolving .. path segments. With only https://api.example.com/v1 authorized, it accepts https://api.example.com/v1/../admin because the rest starts with /, even though the request can reach /admin. Compare the resolved request path with the authorized base.

How this was verified: The matcher accepts the raw /../admin suffix, and the MCP boundary forwards the original arguments.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/scope/schema.py
Line: 320-325

Comment:
**API path check can be escaped**

`_url_prefix_matches` compares the raw URL without resolving `..` path segments. With only `https://api.example.com/v1` authorized, it accepts `https://api.example.com/v1/../admin` because the rest starts with `/`, even though the request can reach `/admin`. Compare the resolved request path with the authorized base.

**How this was verified:** The matcher accepts the raw `/../admin` suffix, and the MCP boundary forwards the original arguments.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix/scope/schema.py
Comment on lines +220 to +223
for base in self.api.base_urls:
if _url_prefix_matches(target, base):
reason = f"matches api.base_urls entry {base!r}"
return ScopeDecision.allow(reason, "api", "api", base)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security API calls skip port limits

The API-base branch returns approval before checking network.ports. If an operator authorizes https://api.example.com:8443/v1 as an API base but allows only port 443, a call to port 8443 passes. Apply the configured port limit before approving this branch.

How this was verified: An API prefix match returns immediately; only the later branches check the port.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/scope/schema.py
Line: 220-223

Comment:
**API calls skip port limits**

The API-base branch returns approval before checking `network.ports`. If an operator authorizes `https://api.example.com:8443/v1` as an API base but allows only port 443, a call to port 8443 passes. Apply the configured port limit before approving this branch.

**How this was verified:** An API prefix match returns immediately; only the later branches check the port.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix/scope/schema.py
Comment on lines +225 to +227
for domain in self.web.domains:
if _host_matches(host, domain):
port_ok, port_reason = self._port_ok(parsed.port)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Bad ports break scope checks

_check_url reads parsed.port without catching ValueError. If an agent checks http://example.com:abc, it gets a tool error instead of a clear scope denial. Return a denied ScopeDecision for an invalid port.

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/scope/schema.py
Line: 225-227

Comment:
**Bad ports break scope checks**

`_check_url` reads `parsed.port` without catching `ValueError`. If an agent checks `http://example.com:abc`, it gets a tool error instead of a clear scope denial. Return a denied `ScopeDecision` for an invalid port.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread strix/core/runner.py
teardown_logging = setup_scan_logging(run_dir)
set_scan_id(scan_id)
# Strix 2: register additive agent tools + load the scope policy at run start.
install_strix2_extensions(run_dir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Failed starts keep log handlers

install_strix2_extensions can raise for a malformed scope file after scan logging starts but before the runner's cleanup block begins. The run leaves its log handlers attached and its file open. Load the policy before opening scan resources, or cover this call with cleanup.

Knowledge Base Used: Scan execution lifecycle

Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/core/runner.py
Line: 231

Comment:
**Failed starts keep log handlers**

`install_strix2_extensions` can raise for a malformed scope file after scan logging starts but before the runner's cleanup block begins. The run leaves its log handlers attached and its file open. Load the policy before opening scan resources, or cover this call with cleanup.

**Knowledge Base Used:** [Scan execution lifecycle](https://app.greptile.com/strix-org-3/-/custom-context/knowledge-base/usestrix/strix/-/docs/scan-execution.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread .github/workflows/ci.yml
Comment on lines +36 to +37
- name: Install uv
uses: astral-sh/setup-uv@v5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 CI actions can change underfoot

The new CI workflow uses mutable action tags, while the existing release workflow pins those actions to commit hashes. A moved tag could change what code CI runs without a repository change. Pin checkout and setup actions here too.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/ci.yml
Line: 36-37

Comment:
**CI actions can change underfoot**

The new CI workflow uses mutable action tags, while the existing release workflow pins those actions to commit hashes. A moved tag could change what code CI runs without a repository change. Pin checkout and setup actions here too.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread THIRD_PARTY.md
Comment on lines +61 to +65
## Landed — Phase 1 wrappers

| Tool | Domain | How wrapped | License | Conf. |
|---|---|---|---|---|
| boto3 (AWS SDK) | cloud API calls (read-only validation PoC) | host-side MCP wrapper `strix/mcp_servers/aws.py`, **imported as a library** (not a subprocess) | Apache-2.0 | ✅ |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Cloud wrapper listed too early

THIRD_PARTY.md lists strix/mcp_servers/aws.py as landed and scope-gated, but this PR contains no strix/mcp_servers package. Mark the wrapper as planned until it lands so readers do not rely on a cloud guard that is not present.

Prompt To Fix With AI
This is a comment left during a code review.
Path: THIRD_PARTY.md
Line: 61-65

Comment:
**Cloud wrapper listed too early**

`THIRD_PARTY.md` lists `strix/mcp_servers/aws.py` as landed and scope-gated, but this PR contains no `strix/mcp_servers` package. Mark the wrapper as planned until it lands so readers do not rely on a cloud guard that is not present.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Adds the authorized-scope policy and enforcement that the other Strix 2 areas
build on, all additive and opt-in (no-op without a scope.yaml):

- strix/scope/: ScopePolicy schema (web/api/network/cloud) + fail-closed loader
  + runtime enforcement (enforce_target/arguments/shell_command).
- strix/tools/scope/: check_scope / scope_status agent tools.
- scope check wired at the call_mcp boundary (tools/mcp/agent_tools.py).
- --scope-config / --allow-intrusive CLI flags.
- strix/strix2_ext.py: the single idempotent install hook (registers Strix 2
  tools + loads the policy), called once from run_strix_scan (the one core edit).
- CI (lint + mypy + Strix 2 tests + baseline), design docs, THIRD_PARTY, a
  scope.yaml template (no live targets).

First of a dependency-ordered series splitting usestrix#1394; the candidate tier,
cloud wrapper, and efficiency/eval areas build on this.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@Pocs321
Pocs321 force-pushed the strix2-foundation branch from a75aa77 to 56caebf Compare October 1, 2026 06:29

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant