mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-13 16:38:56 +02:00
fix(one-way-doors): unify credential noun net + wire it into the runtime (#2024)
Library fix: revoke/reset/rotate now share ONE noun alternation (api key,
token, secret, credential, access key, password) with optional plural s?.
Pre-fix leaks: "reset my secret", "reset my access key", "revoke my secret"
(mismatched per-verb lists) and every plural form ("rotate the credentials",
"revoke all tokens" — \b(...)\b cannot match a trailing s).
Runtime wiring — the regexes could never fire in production before:
- gstack-question-preference --check gains --summary-stdin: the question
text pipes via stdin (never argv — summaries carry quotes/newlines/shell
metacharacters) and feeds isOneWayDoor alongside the id, so an ad-hoc
destructive question with a stored never-ask preference now forces
ASK_NORMALLY. Empty/absent stdin keeps exact id-only semantics.
- question-preference-hook falls back to classifyQuestion(question text)
when the registry lookup misses, so unregistered destructive questions
pass through to a human instead of auto-deciding.
- question-tuning resolver prose shows the piped form (SKILL.md regen lands
in the wave's release commit).
Tripwires (verified fail-first): full verbs x nouns x singular/plural matrix
with the #2024 repro rows, benign-summary no-over-match rows, stdin
transport survival (quotes/newlines), empty-stdin fail-safe, and hook
fallback both directions (destructive -> pass-through, benign -> deny).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3d97863b14
commit
e0662ea7b5
@@ -45,6 +45,7 @@ import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { isConductor } from '../../../lib/is-conductor';
|
||||
import { classifyQuestion } from '../../../scripts/one-way-doors';
|
||||
|
||||
interface HookStdin {
|
||||
session_id?: string;
|
||||
@@ -434,7 +435,21 @@ async function main(): Promise<void> {
|
||||
if (!pref.preference || pref.preference === 'always-ask') { fullyAutoDecidable = false; break; }
|
||||
|
||||
const entry = registry[questionId];
|
||||
const doorType = entry?.door_type || 'two-way';
|
||||
let doorType: string = entry?.door_type || 'two-way';
|
||||
if (!entry) {
|
||||
// #2024: an unregistered id used to default straight to two-way without
|
||||
// consulting the keyword net, so an ad-hoc DESTRUCTIVE question with a
|
||||
// stored never-ask preference auto-decided. classifyQuestion is a pure
|
||||
// regex pass over the question text; on any failure keep the default
|
||||
// (enforcement still requires an explicit stored preference).
|
||||
try {
|
||||
if (classifyQuestion({ summary: qText.replace(MARKER_RE, '').trim() }).oneWay) {
|
||||
doorType = 'one-way';
|
||||
}
|
||||
} catch (e) {
|
||||
logHookError(`one-way classifier failed: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
// Safety override — even never-ask doesn't bypass one-way doors.
|
||||
if (doorType === 'one-way') { fullyAutoDecidable = false; break; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user