MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / isReadOnlyTool

Method isReadOnlyTool

src/security/permissions.ts:113–139  ·  view source on GitHub ↗

* Check if a tool is read-only. Uses the registry callback if available * (so new tools are automatically recognized via their `isReadOnly` property). * Falls back to a hardcoded list for back-compat.

(tool: string)

Source from the content-addressed store, hash-verified

111 }
112
113 /**
114 * Persist a decision. Scopes:
115 * - 'once' — just for this call (no-op here; caller acts)
116 * - 'session' — until QodeX restart, for THIS exact tool:operation pair
117 * - 'pattern' — until QodeX restart, for anything matching the command prefix
118 * - 'tool' — until QodeX restart, ALL invocations of this tool name
119 */
120 rememberDecision(req: PermissionRequest, decision: 'allow' | 'deny', scope: 'once' | 'session' | 'pattern' | 'tool'): void {
121 const key = `${req.tool}:${req.operation}`;
122 if (scope === 'session') {
123 if (decision === 'allow') this.sessionAllows.add(key);
124 else this.sessionDenies.add(key);
125 } else if (scope === 'pattern' && decision === 'allow') {
126 // A grant binds to the EXACT command, not to its first word. The old behaviour built
127 // `^git( |$)` from `git status`, which then auto-approved `git push --force`; and
128 // `^rm( |$)` from `rm -rf /tmp/x`, which auto-approved `rm -rf /`. That is the one
129 // failure mode rollback cannot undo — the journal covers file writes, not shell
130 // commands — so "always" now means "this command", nothing broader.
131 if (canGrantAlways(req.operation).allowed) {
132 this.commandGrants.add(normalizeCommand(req.operation));
133 }
134 // Irreversible commands deliberately get NO standing grant: they are asked every time.
135 } else if (scope === 'tool' && decision === 'allow') {
136 this.sessionToolAllows.add(req.tool);
137 }
138 }
139
140 /** Why a standing grant was refused, for the UI to explain instead of silently not saving. */
141 grantRefusalReason(operation: string): string | null {
142 return canGrantAlways(operation).reason ?? null;

Callers 1

evaluateMethod · 0.95

Calls 2

getMethod · 0.45
setMethod · 0.45

Tested by

no test coverage detected