Skip to content

feat(policy-engine) : built policy engine#4

Merged
yb175 merged 4 commits into
mainfrom
policy-engine
Jun 24, 2026
Merged

feat(policy-engine) : built policy engine#4
yb175 merged 4 commits into
mainfrom
policy-engine

Conversation

@yb175

@yb175 yb175 commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Introduces a policy engine to gate tool execution with ALLOW/DENY/PENDING and human approvals. Adds /policies CRUD and mounts the router; /mcp/execute still trusts request-supplied decisions (no decide() wiring yet).

  • New Features

    • Chained block, budget, and approval rules with fail-closed handling and single policy prefetch passed to rules.
    • Decision layer that creates/fetches approval records, validates tool_name, and consumes approvals on use.
    • /policies CRUD endpoints (list, get, create, update, delete) with implicit APPROVAL for unknown tools; router mounted.
    • Types/docs/tests: re-exports ApprovalStatus from @repo/db; adds ApprovalRequest, RuleResult, ConversationRequest; adds @repo/db README; tests for rules, engine, decisions, and routes.
  • Bug Fixes

    • Approved human approvals are deleted after use to prevent replay.
    • Budget rule fails closed when conversationId is missing or "unknown"; non-existent explicit IDs also fail closed.

Written for commit e9680bb. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR introduces a policy engine for gating MCP tool executions with ALLOW/DENY/PENDING decisions and human-in-the-loop approvals, along with /policies CRUD endpoints. Several issues flagged in the previous review round have been resolved: approval records are now consumed (deleted) on use, tool_name is validated against the approval record, the pre-fetched policy is correctly passed to all rule checkers (resolving the TOCTOU concern), and unknown tools now correctly default to requiring APPROVAL rather than executing freely.

  • New policy engine (engine.ts, decision.ts, rules/) chains block → budget → approval checks using a single pre-fetched policy record, and creates/validates approval records in decide().
  • REST CRUD (router.ts, index.ts) provides /policies list/get/create/update/delete with enum validation and conflict detection; mounted globally at app startup.
  • decide() is not yet wired to POST /mcp/execute — the executor still reads decision directly from the request body, so the entire policy engine can currently be bypassed by any caller; this is acknowledged as deferred work.

Confidence Score: 4/5

The policy engine module is internally correct, but the executor still reads the decision from the request body rather than calling decide(), leaving the gate wide open on the actual execution path.

The policy engine logic itself (chained rules, TOCTOU fix, approval consumption, tool-name validation) is sound and the tests are comprehensive. However, POST /mcp/execute never calls decide() — any caller can send {"decision":"ALLOW"} and bypass every block, budget, and approval rule in this PR. That gap is acknowledged and deferred, but it means the feature is not enforced yet on any real request.

apps/api/mcp/execute.ts and apps/api/src/policy/decision.ts — the missing wiring between the decision engine and the execution handler is the key gap to close next.

Important Files Changed

Filename Overview
apps/api/src/policy/decision.ts New decision orchestrator: correctly validates tool_name on approval lookup and deletes consumed approvals. Catch block swallows errors without logging them.
apps/api/src/policy/engine.ts Policy engine now correctly pre-fetches policy once and passes it to all rule checkers, resolving the TOCTOU concern. Second catch block swallows unexpected rule errors without logging them.
apps/api/src/policy/rules/approval.ts Correctly defaults unknown tools to APPROVAL (not ALLOW), accepts pre-fetched policy to avoid redundant DB round-trips, uses logger for error reporting.
apps/api/src/policy/rules/budget.ts Correctly guards against unknown/missing conversationId and uses logger for DB errors. Token counter increment is deferred to a future module per dev acknowledgement.
apps/api/src/policy/router.ts Clean CRUD router with proper input validation, enum enforcement, and conflict detection. Auth is intentionally omitted for this assignment.
apps/api/types.ts Adds ApprovalRequest, RuleResult, and ConversationRequest types. ApprovalRequest.status is never consumed anywhere in the decision flow and could mislead future maintainers.
apps/api/src/policy/policy.test.ts Comprehensive tests covering block/budget/approval rules, engine orchestration, decision flow, and REST endpoints including the tool_name mismatch case.
apps/api/mcp/execute.ts Reformatting only; no logic changes. Policy engine is still not connected to the execution path (acknowledged as future work).
packages/db/README.md New documentation covering schema architecture with ER diagram, field descriptions, and workflow commands. Accurate and well-structured.

Comments Outside Diff (2)

  1. apps/api/src/index.ts, line 42-60 (link)

    P1 Policy engine never called for tool execution

    The POST /mcp/execute handler reads decision directly from req.body and passes it straight to mcpExecutor.execute(). No call to decide() is made, so any client can send {"decision": "ALLOW"} and bypass all block, budget, and approval rules entirely. The policy engine built in this PR is completely disconnected from the execution path.

  2. apps/api/types.ts, line 69-73 (link)

    P2 ApprovalStatus enum duplicated between types.ts and @repo/db

    @repo/db already exports ApprovalStatus (imported in decision.ts). Redeclaring it locally in types.ts means two independent sources of truth for the same values. If the DB schema enum changes, only the Prisma-generated version updates automatically; the local copy will silently diverge.

Reviews (4): Last reviewed commit: "fix(reviews) : fixed neccassary reviews" | Re-trigger Greptile

Comment thread apps/api/src/policy/decision.ts
Comment thread apps/api/src/policy/rules/approval.ts
Comment thread apps/api/src/policy/rules/budget.ts
Comment thread apps/api/src/policy/rules/block.ts Outdated
Comment thread apps/api/src/policy/rules/block.ts Outdated
Comment thread README.md

@cubic-dev-ai cubic-dev-ai 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.

15 issues found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/policy/rules/budget.ts">

<violation number="1" location="apps/api/src/policy/rules/budget.ts:20">
P1: `token` is used without validation in budget calculation. Negative/NaN values can bypass budget-deny logic.</violation>

<violation number="2" location="apps/api/src/policy/rules/budget.ts:21">
P1: The budget check reads `tokens_used` but nothing in the execution path increments it after a successful tool call. Every subsequent invocation will see the same stale counter, meaning budget limits are effectively never enforced across repeated calls in a conversation.</violation>
</file>

<file name="apps/api/src/index.ts">

<violation number="1" location="apps/api/src/index.ts:16">
P1: Policy management routes are exposed without authentication/authorization. This allows unauthorized clients to create/update/delete enforcement policies.</violation>
</file>

<file name="apps/api/src/policy/engine.ts">

<violation number="1" location="apps/api/src/policy/engine.ts:75">
P1: Unknown tools can be implicitly allowed. Missing policy currently reaches default ALLOW instead of requiring approval.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/api/src/policy/decision.ts
Comment thread apps/api/src/index.ts

app.use(cors());
app.use(express.json({ limit: "1mb" }));
app.use(policiesRouter);

@cubic-dev-ai cubic-dev-ai Bot Jun 24, 2026

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: Policy management routes are exposed without authentication/authorization. This allows unauthorized clients to create/update/delete enforcement policies.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/index.ts, line 16:

<comment>Policy management routes are exposed without authentication/authorization. This allows unauthorized clients to create/update/delete enforcement policies.</comment>

<file context>
@@ -12,6 +13,7 @@ const port = process.env.PORT || 3001;
 
 app.use(cors());
 app.use(express.json({ limit: "1mb" }));
+app.use(policiesRouter);
 
 // Health check endpoint
</file context>
Fix with cubic

};
}

const isExceeded =

@cubic-dev-ai cubic-dev-ai Bot Jun 24, 2026

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: token is used without validation in budget calculation. Negative/NaN values can bypass budget-deny logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/policy/rules/budget.ts, line 20:

<comment>`token` is used without validation in budget calculation. Negative/NaN values can bypass budget-deny logic.</comment>

<file context>
@@ -0,0 +1,36 @@
+      };
+    }
+
+    const isExceeded =
+      conversation.tokens_used + token > conversation.budget_limit;
+
</file context>
Fix with cubic

Comment thread apps/api/src/policy/engine.ts
}

const isExceeded =
conversation.tokens_used + token > conversation.budget_limit;

@cubic-dev-ai cubic-dev-ai Bot Jun 24, 2026

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: The budget check reads tokens_used but nothing in the execution path increments it after a successful tool call. Every subsequent invocation will see the same stale counter, meaning budget limits are effectively never enforced across repeated calls in a conversation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/policy/rules/budget.ts, line 21:

<comment>The budget check reads `tokens_used` but nothing in the execution path increments it after a successful tool call. Every subsequent invocation will see the same stale counter, meaning budget limits are effectively never enforced across repeated calls in a conversation.</comment>

<file context>
@@ -0,0 +1,36 @@
+    }
+
+    const isExceeded =
+      conversation.tokens_used + token > conversation.budget_limit;
+
+    return {
</file context>
Fix with cubic

Comment thread apps/api/src/policy/router.ts
Comment thread apps/api/src/policy/router.ts Outdated
Comment thread apps/api/src/policy/policy.test.ts Outdated
Comment thread apps/api/src/policy/rules/approval.ts Outdated
Comment thread apps/api/src/policy/rules/block.ts Outdated
Comment thread apps/api/src/policy/decision.ts
Comment thread apps/api/src/policy/rules/budget.ts

@cubic-dev-ai cubic-dev-ai 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.

5 issues found across 10 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/policy/rules/budget.ts">

<violation number="1" location="apps/api/src/policy/rules/budget.ts:20">
P1: `token` is used without validation in budget calculation. Negative/NaN values can bypass budget-deny logic.</violation>

<violation number="2" location="apps/api/src/policy/rules/budget.ts:21">
P1: The budget check reads `tokens_used` but nothing in the execution path increments it after a successful tool call. Every subsequent invocation will see the same stale counter, meaning budget limits are effectively never enforced across repeated calls in a conversation.</violation>
</file>

<file name="apps/api/src/index.ts">

<violation number="1" location="apps/api/src/index.ts:16">
P1: Policy management routes are exposed without authentication/authorization. This allows unauthorized clients to create/update/delete enforcement policies.</violation>
</file>

<file name="apps/api/src/policy/engine.ts">

<violation number="1" location="apps/api/src/policy/engine.ts:75">
P1: Unknown tools can be implicitly allowed. Missing policy currently reaches default ALLOW instead of requiring approval.</violation>

<violation number="2" location="apps/api/src/policy/engine.ts:79">
P2: Using one cached policy for the approval stage creates a stale-decision window. A request can miss stricter policy updates applied during evaluation.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/api/src/policy/rules/approval.ts Outdated
Comment thread apps/api/src/policy/decision.ts
Comment thread apps/api/src/policy/rules/budget.ts Outdated
Comment thread apps/api/src/policy/router.ts
Comment thread apps/api/src/policy/engine.ts

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/policy/rules/budget.ts">

<violation number="1" location="apps/api/src/policy/rules/budget.ts:20">
P1: `token` is used without validation in budget calculation. Negative/NaN values can bypass budget-deny logic.</violation>

<violation number="2" location="apps/api/src/policy/rules/budget.ts:21">
P1: The budget check reads `tokens_used` but nothing in the execution path increments it after a successful tool call. Every subsequent invocation will see the same stale counter, meaning budget limits are effectively never enforced across repeated calls in a conversation.</violation>
</file>

<file name="apps/api/src/index.ts">

<violation number="1" location="apps/api/src/index.ts:16">
P1: Policy management routes are exposed without authentication/authorization. This allows unauthorized clients to create/update/delete enforcement policies.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/policy/engine.ts Outdated
Comment thread apps/api/src/policy/rules/budget.ts
Comment thread apps/api/src/policy/engine.ts Outdated
Comment thread apps/api/src/policy/router.ts
@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@yb175 yb175 merged commit 9f11c0a into main Jun 24, 2026
3 checks passed
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