Skip to content

feat: enforcement gateway v2 - agent-facing MCP server with human-only approval#58

Open
jessfortemnaturae8717 wants to merge 1 commit into
mainfrom
feat/enforcement-gateway-v2
Open

feat: enforcement gateway v2 - agent-facing MCP server with human-only approval#58
jessfortemnaturae8717 wants to merge 1 commit into
mainfrom
feat/enforcement-gateway-v2

Conversation

@jessfortemnaturae8717
Copy link
Copy Markdown
Member

Summary

Complete enforcement layer rewrite with the core invariant:

The agent proposes. The gateway enforces. RecourseOS verifies consequences.

  • Agents receive gateway tools, never raw credentials
  • gateway_approve/gateway_reject removed (human-only control plane)
  • Terraform is plan-bound (plan_id verification, hash, TTL, approval)
  • kubectl split into operation-specific tools (get/logs vs apply/delete)
  • Shell sandboxed with explicit allow/escalate/block lists
  • Policy model: per-environment rules, protected namespaces

New Commands

recourse gateway serve -e prod      # Start enforcement gateway
recourse gateway doctor -e prod     # Run 28 self-tests

Gateway Doctor Output

✓ gateway_approve not exposed
✓ gateway_reject not exposed
✓ raw terraform/kubectl tools not exposed
✓ Terraform apply requires plan_id
✓ Terraform apply with expired plan_id fails
✓ Terraform apply without approval fails
✓ kubectl exec escalates by default
✓ kubectl delete namespace blocks
✓ Shell: curl | sh blocks
✓ Shell: rm -rf / blocks
... (28 tests total)

All tests passed - Gateway is production-ready

Trust Boundary

The agent does NOT receive raw Terraform, Kubernetes, shell, or cloud credentials.
The agent receives ONLY gateway tools.
The gateway owns execution credentials and applies policy, consequence evaluation,
approval checks, and audit logging before any mutation is executed.

Test plan

  • npm run build passes
  • recourse gateway doctor -e prod passes (28/28 tests)
  • tools/list does not expose gateway_approve or gateway_reject
  • terraform apply without plan_id fails
  • kubectl exec escalates
  • shell curl|bash blocks

🤖 Generated with Claude Code

…y approval

This is a complete enforcement layer rewrite with the core invariant:
"The agent proposes. The gateway enforces. RecourseOS verifies consequences."

Key changes:
- Agents receive gateway tools, never raw credentials
- gateway_approve/reject removed (human-only control plane)
- Terraform is plan-bound (plan_id verification, hash, TTL, approval)
- kubectl split into operation-specific tools (get/logs vs apply/delete)
- Shell sandboxed with explicit allow/escalate/block lists
- Policy model: per-environment rules, protected namespaces

New commands:
- recourse gateway serve -e prod
- recourse gateway doctor -e prod (28 self-tests)

Trust boundary documented in README and docs/enforcement-gateway.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1c56735fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/mcp-server.ts
Comment on lines +321 to +322
if (options.policyFile && fs.existsSync(options.policyFile)) {
// TODO: Load from YAML
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Load the policy file before serving requests

runGatewayMcpServer accepts --policy/policyFile, but this branch is a no-op, so the server always runs with defaults even when operators supply stricter production rules. That silently disables custom enforcement (protected namespaces, TTLs, shell blocks, etc.), which is a security/control-plane regression because callers believe policy overrides are active when they are not.

Useful? React with 👍 / 👎.

Comment thread src/gateway/mcp-server.ts

// Run terraform plan
const planFile = path.join(cwd, `tfplan-${Date.now()}`);
const planResult = await runCommand('terraform', ['plan', '-out=' + planFile, ...extraArgs], cwd);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor workspace input when creating Terraform plans

gateway_terraform_plan accepts a workspace argument, but the plan command is executed without selecting or passing that workspace. As a result, plans run in whichever workspace is currently active, so the stored workspace metadata can be wrong and protections like workspace-specific approval policy can be bypassed unintentionally.

Useful? React with 👍 / 👎.

Comment thread src/gateway/mcp-server.ts
Comment on lines +834 to +836
const kubectlArgs = ['apply'];
if (file) kubectlArgs.push('-f', file);
if (namespace) kubectlArgs.push('-n', namespace);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add stdin source flag for inline kubectl manifests

When gateway_kubectl_apply is called with manifest (and no file), the code writes YAML to stdin but never adds -f - to kubectl apply. In this path kubectl has no manifest source argument, so inline-manifest applies fail at runtime instead of executing the evaluated change.

Useful? React with 👍 / 👎.

Comment thread src/gateway/mcp-server.ts
options: GatewayMcpServerOptions = {}
): Promise<void> {
verbose = options.verbose ?? false;
currentEnvironment = options.environment ?? 'dev';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate environment values before using policy maps

The server trusts options.environment and stores it directly in currentEnvironment without normalization/validation. If callers pass values like production (which the CLI help text suggests), later lookups such as policy.environments[currentEnvironment] become undefined and mutation handlers return runtime errors instead of enforceable decisions.

Useful? React with 👍 / 👎.

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