|
oc_chat
|
Classes | |
| class | ArgvDecision |
Functions | |
| ArgvDecision | _deny (list[str] argv, str program, str reason) |
| str | _metachar_denial (frozenset[str] chars) |
| tuple[frozenset[str], frozenset[str], str] | _scan_metachars (str raw) |
| bool | _is_plain_name (str token) |
| list[str] | _normalize_options (list[str] tokens, frozenset[str] value_opts) |
| ArgvDecision | classify_command (str command, Path sandbox_root) |
| bool | _short_cluster_names_index (str token) |
| str | pip_index_option (list[str] tokens) |
| str | pip_index_denial (str token) |
| ArgvDecision | _classify_pip (list[str] argv, list[str] rest) |
| ArgvDecision | _classify_where (list[str] argv, str program, list[str] rest) |
| ArgvDecision | _classify_listing (list[str] argv, str program, list[str] rest, Path sandbox_root) |
Variables | |
| frozenset | _SHELL_METACHARS = frozenset("|&;<>$`%\n\r\0") |
| frozenset | _TIER2_PROGRAMS = frozenset({"python", "python3", "py", "pytest", "oct"}) |
| frozenset | WINDOWS_BUILTINS = frozenset({"dir", "echo"}) |
| frozenset | PIP_INDEX_REACHING_SUBCOMMANDS = frozenset({"install", "download"}) |
| tuple | _PIP_INDEX_LONG_OPTIONS |
| frozenset | _PIP_INDEX_SHORT_OPTIONS = frozenset({"i", "f"}) |
| frozenset | _PIP_SHORT_VALUE_OPTIONS = frozenset({"r", "c", "e", "t", "b", "d"}) |
| frozenset | _PIP_SHORT_FLAG_OPTIONS = frozenset({"v", "q", "U", "I", "h"}) |
| tuple | _PIP_INDEX_DENIAL_CLAUSE |
| frozenset | _QUOTED_METACHAR_PROGRAMS = _TIER2_PROGRAMS |
Whole-argv command policy (M8.0 WU-3; Appendix A / LD-4).
Purpose
-------
Replaces the legacy longest-prefix ``startswith`` whitelist + ``shell=True``
execution with a deterministic, whole-argv allowlist evaluated against the
locked Appendix A table. The model's command string is:
1. scanned for shell metacharacters (``| & ; < > $`` / backtick / ``%`` /
newline / NUL) with POSIX quote tracking. Any metacharacter OUTSIDE quotes is
rejected outright, for every program -- there is no ``shell=True`` lane, so
these can only be injection (``git status && x``, ``echo $(cat .env)``, pipes,
redirects). A metacharacter found ONLY inside a quoted argument is admitted
for the directly-launched Tier-2 programs alone
(:data:`_QUOTED_METACHAR_PROGRAMS`), where nothing re-parses the string and it
reaches the child as data (SHELL-01; operator ruling 2026-09-04, a narrow
named amendment to the M8.1 lock's LD-4 wording). An unterminated quote is a
deny, never a fallthrough, and NUL is denied wherever it appears;
2. parsed to argv via :func:`shlex.split`;
3. dispatched on the (basename, lowercased) program and validated as a WHOLE
argv -- subcommands, options, and path args each checked; an unknown option
at any position, an out-of-sandbox path arg, or an unknown program ⇒ deny.
Responsibilities
----------------
- :func:`classify_command` -- the single entry point: metacharacter gate, parse,
program normalization, then dispatch to a per-program validator.
- :func:`_scan_metachars` -- the quote-aware metacharacter scan (SHELL-01).
- :func:`_metachar_denial` -- the denial that names the character, the reason
and the sanctioned routes.
- :func:`_classify_pip` / :func:`_classify_where` / :func:`_classify_listing` --
the per-program whole-argv validators.
- :func:`_normalize_options` -- canonical option normalization ("normalize then
match"), run before any option is compared.
- :func:`pip_index_option` / :func:`pip_index_denial` -- the package-index and
trusted-host option family, and the denial it earns on ``pip install`` /
``pip download``. Shared with the execution seam in :mod:`oc_chat.tools.shell`,
which pins the index the operator trusts and refuses the ``python -m pip``
spelling of the same command on the same terms (M8 F78, SUPPLY-02).
- :class:`ArgvDecision` -- the frozen decision record the caller acts on.
Tier-1 (allow, ``shell=False``): ``pip list``/``show``/``freeze``,
``where``/``which`` (one bare program name), ``dir``/``ls`` (``-l``/``-a``/
``--color=never`` + ≤1 in-sandbox path), ``echo`` (argv-only). Tier-2 (confirm/
grant): ``python``/``pytest``/``pip install``/``pip uninstall``/``oct``. Reads
go through the filesystem tools and ``git`` through ``oct_git_*`` -- so ``git``
(freeform), ``cat``/``type``/``grep``/``findstr``, ``curl``/``wget``,
``bash``/``sh``/``powershell``/``pwsh``/``node`` and the cmd.exe mutators
(``del``/``copy``/``move``/``ren``/``md``/``rmdir``) are **denied**.
The ``_get_cross_platform_command`` Unix→PowerShell conversion is dropped (F55):
no converted form is admitted.
Diagnostics
-----------
Domain: OC_CHAT.TOOLS
Levels:
L2 -- lifecycle events
L3 -- semantic details (the decision and the reason it carries)
L4 -- deep tracing
Contracts
---------
- Classification is PURE: no writes, no subprocess, no network. The only
external read is :func:`require_inside_sandbox` resolving a path argument.
- Deny is the default. An unknown program, an unknown option at any position,
or an out-of-sandbox path argument denies rather than falling through.
- A pre-parse deny carries an empty ``argv`` -- nothing was parsed yet.
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.tools._sandbox (require_inside_sandbox, SandboxViolation - the sandbox check on a dir or ls path argument)
|
protected |
Definition at line 428 of file _argv_policy.py.
|
protected |
Definition at line 392 of file _argv_policy.py.
|
protected |
Definition at line 422 of file _argv_policy.py.
|
protected |
|
protected |
True if *token* is a bare identifier (no path separators / traversal), suitable as a ``where``/``which`` target or a ``pip show`` package name.
Definition at line 257 of file _argv_policy.py.
|
protected |
Build the metacharacter denial, which must TEACH THE ROUTE (SHELL-01, P3). In order: the offending character(s); one clause saying why (nothing on this path interprets them); then the sanctioned routes, including the fact that a directly-launched Tier-2 program does accept them inside a quoted argument. The raw command is deliberately NOT echoed back -- a denied string may carry text an attacker wrote for the model to read.
Definition at line 177 of file _argv_policy.py.
|
protected |
Canonically normalize option tokens before matching (Appendix A, "normalize then match"): expand bundled short options (``-la`` -> ``-l -a``) and unify space-separated long-option values (``--opt val`` -> ``--opt=val`` for opts in *value_opts*). Leaves positional (path/name) args untouched.
Definition at line 265 of file _argv_policy.py.
|
protected |
Split the shell metacharacters in *raw* by quote state (SHELL-01, P1). Walks the raw string tracking quote state under the SAME rules :func:`shlex.split` applies with ``posix=True``, because a divergence between this scanner and the parser that produces the argv is itself the bug class this guards: single quotes make everything literal until the closing quote; inside double quotes only ``\\"`` and ``\\\\`` are escapes; outside quotes a backslash escapes the next character. Returns ``(outside, inside, error)`` — the metacharacters found outside any quote, those found inside one, and a non-empty *error* when the string cannot be parsed at all (an unterminated quote, a trailing escape). An unparseable string is a DENY at the call site, never a fallthrough: a scanner that cannot say what is quoted must not admit anything. A backslash-escaped metacharacter outside quotes counts as OUTSIDE — fail-closed; only genuine quoting admits.
Definition at line 199 of file _argv_policy.py.
|
protected |
True when a single-dash pip *token* spells ``-i`` or ``-f`` (253 P2). Walks the option letters the way optparse does: a flag letter continues the cluster, a value-taking letter ends it (everything after it is that option's value, not an option letter), and an unknown letter ends it too — pip itself rejects the command in that case, so there is nothing left to guard.
Definition at line 342 of file _argv_policy.py.
| ArgvDecision oc_chat.tools._argv_policy.classify_command | ( | str | command, |
| Path | sandbox_root ) |
Classify *command* against the Appendix A whole-argv allowlist.
Definition at line 288 of file _argv_policy.py.
| str oc_chat.tools._argv_policy.pip_index_denial | ( | str | token | ) |
The denial for a model-named package index — it must TEACH THE ROUTE. Naming the offending token matters: the model has to know which option to drop, and "your command was denied" without it invites the same command back in a different spelling. The route is the operator's, out of band, so the text says so rather than implying a retry could succeed.
Definition at line 378 of file _argv_policy.py.
| str oc_chat.tools._argv_policy.pip_index_option | ( | list[str] | tokens | ) |
The first token in *tokens* naming an index or a trusted host, or ``""``. Recognises every spelling the family has (253 P2): the long options with a space- or ``=``-separated value, an unambiguous long-option ABBREVIATION, ``--no-index``, and the short ``-i`` / ``-f`` alone, bundled behind flags, or with the value attached. ``--`` and a bare ``-`` are not options.
Definition at line 359 of file _argv_policy.py.
|
protected |
Definition at line 143 of file _argv_policy.py.
|
protected |
Definition at line 123 of file _argv_policy.py.
|
protected |
Definition at line 129 of file _argv_policy.py.
|
protected |
Definition at line 138 of file _argv_policy.py.
|
protected |
Definition at line 134 of file _argv_policy.py.
|
protected |
Definition at line 161 of file _argv_policy.py.
|
protected |
Definition at line 102 of file _argv_policy.py.
|
protected |
Definition at line 106 of file _argv_policy.py.
| frozenset oc_chat.tools._argv_policy.PIP_INDEX_REACHING_SUBCOMMANDS = frozenset({"install", "download"}) |
Definition at line 115 of file _argv_policy.py.
| frozenset oc_chat.tools._argv_policy.WINDOWS_BUILTINS = frozenset({"dir", "echo"}) |
Definition at line 110 of file _argv_policy.py.