Skip to content

feat: granular session-scoped allow rules#71

Open
kere-nel wants to merge 2 commits into
mainfrom
feat/granular-allow-session-rules
Open

feat: granular session-scoped allow rules#71
kere-nel wants to merge 2 commits into
mainfrom
feat/granular-allow-session-rules

Conversation

@kere-nel

Copy link
Copy Markdown
Contributor

Add allowForSessionGranular to SecurityRule, enabling per-argument allow overrides scoped to a session key prefix. Lets users approve a specific file path, command, or URL for a cron job session without blanket-allowing the entire module.method.

  • types.ts: add allowForSessionGranular to SecurityRule
  • Interceptor.ts: add extractPrimaryArg(), check granular entries via glob; add reloadPolicy()
  • config.ts: add Session/Memory/Agent default rules; FileSystem.delete DENY -> ASK
  • test/granular-allow.test.mjs: full test coverage

Add allowForSessionGranular to SecurityRule, enabling per-argument
allow overrides scoped to a session key prefix. Lets users approve
a specific file path, command, or URL for a cron job session without
blanket-allowing the entire module.method.

- types.ts: add allowForSessionGranular to SecurityRule
- Interceptor.ts: add extractPrimaryArg(), check granular entries via glob; add reloadPolicy()
- config.ts: add Session/Memory/Agent default rules; FileSystem.delete DENY -> ASK
- test/granular-allow.test.mjs: full test coverage
@kere-nel kere-nel force-pushed the feat/granular-allow-session-rules branch from b579e8a to 6571337 Compare May 7, 2026 21:15
@kere-nel kere-nel marked this pull request as ready for review May 8, 2026 22:00
@greptile-apps

greptile-apps Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces allowForSessionGranular on SecurityRule, letting operators approve specific file paths, shell commands, or URLs for a named session prefix without blanket-allowing the entire module.method. It also adds default rules for Session, Memory, and Agent modules, a reloadPolicy() hot-reload method, and downgrades FileSystem.delete from DENY to ASK in the default policy.

  • src/core/Interceptor.ts: New extractPrimaryArg() resolves the "primary" tool argument (path/URL/command) for glob matching; lookupRule() now checks blanket and granular session-scoped allows before returning the base rule; reloadPolicy() allows live policy swaps.
  • src/config.ts: Three new module groups (Session, Memory, Agent) with read-ALLOW / mutate-ASK defaults; FileSystem.delete changed from DENY to ASK.
  • test/granular-allow.test.mjs: Eight node:test cases covering blanket vs. granular matching, session-prefix scoping, and wildcard globs.

Confidence Score: 3/5

Two present defects on the changed paths need resolution before merging.

The Shell tilde expansion asymmetry in extractPrimaryArg causes argGlob matching to silently fail for commands containing ~, and the FileSystem.delete default change removes an unconditional hard block that existing integrations may rely on without any reconfiguration.

src/core/Interceptor.ts (Shell tilde expansion in extractPrimaryArg) and src/config.ts (FileSystem.delete default policy change) need a second look.

Important Files Changed

Filename Overview
src/core/Interceptor.ts Adds extractPrimaryArg(), reloadPolicy(), and granular session allow logic in lookupRule(). Shell tilde expansion is asymmetric (P1); extractPrimaryArg doc-comment incorrectly states paths are not resolved (P2).
src/config.ts Adds Session, Memory, and Agent default rule groups; changes FileSystem.delete from DENY to ASK — a default-policy security posture downgrade affecting all existing consumers.
src/types.ts Adds allowForSessionKeys and allowForSessionGranular fields to SecurityRule; clean type additions with clear JSDoc.
test/granular-allow.test.mjs Good coverage of blanket and granular session allow paths, but imports compiled dist/ artifacts requiring a prior build, and reloadPolicy has no test coverage.

Reviews (1): Last reviewed commit: "feat: granular session-scoped allow rule..." | Re-trigger Greptile

Comment thread src/core/Interceptor.ts
Comment on lines +60 to +67
if (moduleName === 'Shell') {
const v = params.command ?? params.cmd ?? null;
return v != null ? String(v) : null;
}
const raw = params.path ?? params.file_path ?? params.filePath ?? params.target ?? null;
if (raw != null) return path.resolve(expandTilde(String(raw)));
const first = Object.values(params)[0];
return first != null ? String(first) : null;

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 Shell ~ tilde not expanded before glob match

extractPrimaryArg returns the raw command string for the Shell module (no tilde expansion), but matchesGlob only calls expandTilde on the pattern, not on the str. If an agent invokes a command like ~/scripts/backup.sh and the operator writes argGlob: "~/scripts/backup.sh", the comparison becomes:

  • pattern after expandTilde: /home/user/scripts/backup.sh → regex anchored to that absolute path
  • str: ~/scripts/backup.sh — the tilde is never expanded

The regex test fails and the granular allow is silently skipped, leaving the call to fall through to the original rule. For filesystem paths this is harmless because extractPrimaryArg already calls path.resolve(expandTilde(...)), but the Shell branch needs the same treatment or matchesGlob needs to call expandTilde on the str side too.

Comment thread src/core/Interceptor.ts
Comment on lines +51 to +54
/**
* Extract the primary arg value for a tool call, used for granular session allow matching.
* Returns the raw string value (not resolved) so globs can match commands and URLs too.
*/

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 Misleading comment — filesystem paths ARE resolved, not "raw"

The doc-comment says "Returns the raw string value (not resolved)" but the implementation calls path.resolve(expandTilde(...)) for any non-Shell, non-Browser/Network call. In practice argGlob entries for FileSystem rules must always be absolute paths; a relative pattern like reports/*.csv will never match.

Suggested change
/**
* Extract the primary arg value for a tool call, used for granular session allow matching.
* Returns the raw string value (not resolved) so globs can match commands and URLs too.
*/
/**
* Extract the primary arg value for a tool call, used for granular session allow matching.
* For Browser/Network modules returns the URL as-is; for Shell returns the raw command string.
* For all other modules (e.g. FileSystem) the path is resolved to an absolute form via
* path.resolve(expandTilde()), so argGlob patterns for those modules must also be absolute.
*/

Comment thread src/config.ts
Comment on lines 22 to 25
delete: {
action: 'DENY',
description: 'Deletion is strictly prohibited',
action: 'ASK',
description: 'File deletion requires approval',
},

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 Default policy for FileSystem.delete downgraded from hard DENY to ASK

Before this PR the default rule was an unconditional DENY — callers could not delete files regardless of session or intervention state. Changing it to ASK means deletion is now gated by a human approval prompt (TTY) or the channel approval flow. Any project that relies on the default policy and expects file deletion to be unconditionally blocked will silently lose that guarantee without any config change. The granular cron-session use case can be achieved without touching the default by leaving action: 'DENY' here and having callers supply a custom policy with the allowForSessionGranular entries.

Comment on lines +17 to +19

const require = createRequire(import.meta.url);
const { Interceptor } = require('../dist/core/Interceptor.js');

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 Tests import from dist/ — require a prior build step

Both require('../dist/core/Interceptor.js') and require('../dist/plugin/tool-interceptor.js') reference compiled output. Running npm test on a clean checkout will fail with "Cannot find module" rather than a clear test failure. Consider a pretest build script or switching to tsx/ts-node imports. The new reloadPolicy method also has no test coverage.

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