Refusal memory keys on a 2-word verb class: ssh raw-ovh / docker exec become session-length trip wires after one refusal

Open
#64 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
typescript
Domain
cli, security

Research direction

Start in index.ts at normalizeRefusalTarget around line 2770, priorRefusalFor around 3081, and normalizeGrantTarget around 2852. Trace how refusal keys are stored and how liftRefusals matches them, then verify that compound-aware grant keys distinguish read-only targets while model refusals still expire by evidence fingerprint.

Written by the indexing model from the issue text.

Description

enhancement

Problem

Refusal memory keys on the first two words of the command (normalizeRefusalTarget, index.ts:2770: words.slice(0, words[0] === "git" ? 3 : 2)). On a session that talks to one deploy host, that collapses unrelated operations onto one key.

From the production decisions log, 2026-09-12 to 2026-09-17: 152 blocks carry prior refusal as a contributing reason. Top targets and what actually ran behind them:

refusal target blocks what the blocked commands actually were
ssh raw-ovh 38 wp db query "SELECT …", ls /tmp/sr201-*.php, mysql --version, grep -o RUSH_INTAKE_KEY …, unlink /tmp/sr201-prod-measure.php (all reads or scratch-file ops)
docker exec 64 total, 20 since 09-16 docker exec raw_wordpress sh -c 'cp -a /var/www/html /tmp/… && ls', docker exec raw_wordpress php -r 'define("WP_USE_THEMES…' (read-only probes)
docker compose 28 docker compose exec -T wordpress php -r '…' (read-only)
cat > 12 heredoc scratch-file writes under /tmp

Root cause

The 2-word key makes ssh <host> one permanent trigger for the session, whether the refused command was a destructive remote SQL delete or a SELECT. Human/critical/cap refusals do not expire mid-session (priorRefusalFor, index.ts:3081: only source !== "model" refusals clear on a changed evidence fingerprint; the rest are sticky until explicitly approved or the session ends), so one noisy early refusal of a remote cleanup turns every later read-only ssh inspection into a forced dialog for the rest of a long session.

The comment at index.ts:3092 already states the intent — "Model refusals are a warning about the facts the judge saw, not a permanent ban on a normalized verb" — but the same sentence's logic stops at model-source refusals, and the normalized key is too coarse for the fallback cases to be right either: a human refusing ssh raw-ovh '<destructive SQL>' did not thereby refuse ssh raw-ovh 'ls /tmp'.

Proposal

Move the human/critical/cap fallback from "same 2-word verb class" to "same key as the grant path already uses." normalizeGrantTarget (index.ts:2852) exists for exactly this reason and says so in its own comment: refusing to notice that difference turned a grant into an overgrant. Refusal over-match is the same bug in the safe direction — it costs dialogs, not data, but it costs them every session, and it is the single largest named refusal class in the log.

Concretely: index refusals by normalizeGrantTarget (verb + flags + first arg, compound-aware) instead of normalizeRefusalTarget, keep the evidence-fingerprint expiry for model refusals, and let liftRefusals keep working unchanged (it already clears by normalizedTarget + cwd, so tightening the key only narrows what it lifts).

Non-goal

Not asking to weaken refusal memory for genuinely destructive commands. The measured complaint is dialogs on repeated read-only inspection of the same host/container in a session that also refused one destructive operation once. A tighter key preserves the sticky-human rule; it just stops making ssh <host> and docker exec into session-length trip wires.

Related

  • normalizeRefusalTarget and normalizeGrantTarget deliberately disagree today (index.ts:2843 comment calls the over-match "the SAFE direction for refusal memory"). This issue asks to close that gap now that the log shows its size.
  • Measured friction class, not hypothetical: 152 blocks since 09-12, ~9% of all blocks in the window.
  • Interaction with omp-classifier#63: none directly, but the same log shows the git-flow family also false-asks; that issue and this one are the top two measured dialog sources.
Dominant language
TypeScript
Stars
0
Forks
1
Avg merge
1h 20m
Merged PRs (30d)
32

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from STRML/omp-classifier

All issues in STRML/omp-classifier

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.