mirror of
https://github.com/SikongJueluo/pi-extensions.git
synced 2026-10-05 11:52:55 +08:00
feat(pi-permission-inner-cmd): authorize inner commands behind timeout wrappers
- recover the full bash command from the session by tool-call id - add recognizer for the strict timeout wrapper grammar - add authorizer mapping inner allow/ask/deny and forwarding agent name - defer fail-closed on session mismatch, nested wrappers, and errors - add unit tests for recovery, recognizer, authorizer, and lifecycle - document the decision in ADR 0001
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* V0.1 wrapper recognizer.
|
||||
*
|
||||
* The strict simple-timeout grammar from ADR 0001. V0.1 unwraps exactly
|
||||
* `timeout <duration> <command>`; every other `timeout` invocation is left to
|
||||
* the next authority.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Matches `timeout <duration> <command>` where `<duration>` is a positive
|
||||
* integer (no leading zero) followed by a single unit `s`/`m`/`h`/`d`.
|
||||
*
|
||||
* Flags (`-k`, `--preserve-status`, GNU `--`), compound durations, and the
|
||||
* bare form are intentionally excluded so v0.1 never unwraps a wrapper it
|
||||
* cannot re-evaluate safely.
|
||||
*/
|
||||
const TIMEOUT_WRAPPER_PATTERN = /^timeout[ \t]+([1-9][0-9]*[smhd])[ \t]+(.+)$/;
|
||||
|
||||
/** A command that begins with the bare `timeout` wrapper program. */
|
||||
const TIMEOUT_PREFIX = /^timeout(?:[ \t]|$)/;
|
||||
|
||||
export interface TimeoutWrapperMatch {
|
||||
readonly duration: string;
|
||||
readonly innerCommand: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a command as the strict simple-timeout wrapper.
|
||||
*
|
||||
* @returns the duration token and the full inner command (including any
|
||||
* `&&`/`;`/`|` siblings), or `undefined` when the command is not the
|
||||
* recognized `timeout <duration> <command>` form.
|
||||
*/
|
||||
export function parseTimeoutWrapper(
|
||||
command: string,
|
||||
): TimeoutWrapperMatch | undefined {
|
||||
const match = TIMEOUT_WRAPPER_PATTERN.exec(command);
|
||||
if (match === null) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
duration: match[1],
|
||||
innerCommand: match[2],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a command is itself a recognized wrapper. Used to reject nested
|
||||
* wrappers so v0.1 unwraps at most one level.
|
||||
*/
|
||||
export function isRecognizedWrapper(command: string): boolean {
|
||||
return parseTimeoutWrapper(command) !== undefined;
|
||||
}
|
||||
|
||||
/** How a recovered Bash command relates to the v0.1 recognizer. */
|
||||
export type WrapperClassification =
|
||||
| { readonly kind: "recognized"; readonly match: TimeoutWrapperMatch }
|
||||
| { readonly kind: "unsupportedTimeout" }
|
||||
| { readonly kind: "nonTimeout" };
|
||||
|
||||
/**
|
||||
* Classify a recovered Bash command against the v0.1 recognizer.
|
||||
*
|
||||
* - `recognized`: the strict simple-timeout wrapper.
|
||||
* - `unsupportedTimeout`: the command invokes `timeout` but is not the
|
||||
* recognized strict form (flags, `-k`, missing command, ...). These are
|
||||
* logged at debug so an operator can see why a wrapper was skipped.
|
||||
* - `nonTimeout`: an ordinary command this authorizer does not handle. These
|
||||
* defer silently.
|
||||
*/
|
||||
export function classifyWrapper(command: string): WrapperClassification {
|
||||
const match = parseTimeoutWrapper(command);
|
||||
if (match !== undefined) {
|
||||
return { kind: "recognized", match };
|
||||
}
|
||||
if (TIMEOUT_PREFIX.test(command)) {
|
||||
return { kind: "unsupportedTimeout" };
|
||||
}
|
||||
return { kind: "nonTimeout" };
|
||||
}
|
||||
Reference in New Issue
Block a user