|
oc_chat
|
Classes | |
| class | SandboxViolation |
| class | LineRedactor |
Functions | |
| Path | _resolve_default_root () |
| None | set_session_sandbox_root_override (str|Path|None path) |
| None | install_resolved (Path|None resolved) |
| Path|None | get_session_sandbox_root_override () |
| bool | is_path_inside_sandbox (str|Path path, Path|None root=None) |
| Path | require_inside_sandbox (str|Path path, Path|None root=None) |
| str | _norm_component (str name) |
| bool | is_protected_read_path (str|Path path) |
| bool | is_protected_write_path ("str | Path" path) |
| bool | is_human_confirm_write_path ("str | Path" path) |
| bool | is_importable_write_path ("str | Path" path) |
| str | redact_secrets (str text) |
| "dict[str, str]" | scrubbed_env ("dict[str, str] | None" extra=None) |
Variables | |
| Path | DEFAULT_SANDBOX_ROOT = _resolve_default_root() |
| Path | _SESSION_SANDBOX_ROOT_OVERRIDE = None |
| frozenset | _PROTECTED_READ_NAMES |
| tuple | _PROTECTED_READ_SUFFIXES = (".pem", ".key", ".pfx", ".p12") |
| frozenset | _PROTECTED_READ_DIRS = frozenset({".ssh", ".gnupg", ".aws"}) |
| tuple | _SECRETY_STEMS |
| frozenset | _PROTECTED_WRITE_NAMES |
| frozenset | _LOCKFILE_NAMES |
| frozenset | _STARTUP_SCRIPT_NAMES |
| str | _SECRET_REDACTION_PLACEHOLDER = "[REDACTED-SECRET]" |
| str | _PEM_REDACTION_PLACEHOLDER = "[REDACTED-PRIVATE-KEY]" |
| str | _PEM_BEGIN = r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----" |
| str | _PEM_END = r"-----END [A-Z0-9 ]*PRIVATE KEY-----" |
| list | _SECRET_PATTERNS |
| _PEM_BEGIN_RE = re.compile(_PEM_BEGIN) | |
| _PEM_END_RE = re.compile(_PEM_END) | |
Sandbox policy — path clamping (H.2).
Purpose
-------
Single source of truth for one H.2 policy:
1. **Path sandbox** (H.2a) — which filesystem locations may be written
to by tool callers (LLM agents). Paths are clamped to a configurable
``OC_CHAT_SANDBOX_ROOT`` (or the current working directory by
default). Symlinks and ``..`` escapes are resolved before the check
via ``Path.resolve(strict=False)``, so a path that *resolves*
outside the root is rejected even if the literal string looks safe.
Responsibilities
----------------
- :class:`SandboxViolation` — exception raised when a path resolves
outside the configured sandbox root.
- :func:`is_path_inside_sandbox` — predicate; ``True`` if the resolved
path is inside *root*.
- :func:`require_inside_sandbox` — resolve the path and return it;
raise :class:`SandboxViolation` otherwise.
- :func:`redact_secrets` — whole-text secret redaction (M8 F46/F51).
- :class:`LineRedactor` — the same redaction applied LINE BY LINE, with the
multi-line private-key block tracked across calls; one instance per output
stream (plan 287).
Diagnostics
-----------
Domain: OC_CHAT.TOOLS
Levels:
L2 — lifecycle events
L3 — semantic details (sandbox violation)
L4 — deep tracing
Contracts
---------
- :func:`is_path_inside_sandbox` and :func:`require_inside_sandbox`
call ``Path.resolve(strict=False)`` so the path does not need to
exist; this is required because write operations target paths that
are about to be created.
- A relative input path is interpreted relative to *root*, not the
process's current working directory. This matches how the historic
:func:`oc_chat.tools.filesystem._safe_path` joins paths to
``ROOT``.
- The sandbox-root env-var default is re-read at call time, not import
time, so ``monkeypatch.setenv`` works in tests.
- :class:`LineRedactor` adds state, never patterns: its BEGIN and END forms
are the two halves the table's PEM entry is itself composed from, so the
per-line filter and the whole-text one cannot drift. A caller holds ONE
instance per stream; an instance is single-threaded by design.
Dependencies
------------
- oc_diagnostics (structured debug logging via ``_dbg``)
- oc_chat.core.deployment_shape (lazy; enumerate_importable_roots - the importable roots a .py write target is checked against)
|
protected |
Normalize a single path component for protected-class matching.
Strips a Windows alternate-data-stream suffix (``.env::$DATA``) AND the
trailing dots/spaces that the Win32 layer removes at open time (so
``read_file(".env ")`` / ``write_file("conftest.py ")`` open the real file),
then casefolds. Over-stripping on POSIX only ever over-denies (safe).
Definition at line 209 of file _sandbox.py.
|
protected |
Return the default sandbox root, re-reading the env var each call. Order of precedence: 1. ``OC_CHAT_SANDBOX_ROOT`` environment variable. 2. Process current working directory (``os.getcwd()``).
Definition at line 79 of file _sandbox.py.
| Path | None oc_chat.tools._sandbox.get_session_sandbox_root_override | ( | ) |
Return the process-local session sandbox override, if set.
Definition at line 128 of file _sandbox.py.
| None oc_chat.tools._sandbox.install_resolved | ( | Path | None | resolved | ) |
Install an ALREADY-RESOLVED session sandbox override — no-fail (plan 152 D4). The COMMIT-phase counterpart to :func:`set_session_sandbox_root_override`. That function re-runs ``Path().expanduser().resolve(strict=False)``, which is fallible and therefore cannot sit inside a transition's no-fail critical section. A transition resolves the path once during PREPARE and hands the resulting :class:`~pathlib.Path` here, so this is a bare assignment that cannot raise. *resolved* must already be resolved; ``None`` clears the override. :func:`set_session_sandbox_root_override` stays for legacy callers.
Definition at line 111 of file _sandbox.py.
| bool oc_chat.tools._sandbox.is_human_confirm_write_path | ( | "str | Path" | path | ) |
Return ``True`` if *path* is a config-as-code human-confirm-tier write target (``*settings*.json`` incl. ``.oc_chat``/``.claude`` settings, ``pyproject.toml``, a dependency lockfile, a shell-init startup script, the provider-endpoint config ``endpoints.toml``, the local-fetch port allowlist ``local_fetch.json`` — F72/§5.1.2). Hard-deny (the protected-write class) takes precedence and is checked first by the caller; this is the softer tier (the react-loop relays a y/N showing the write content so a human can inspect e.g. the ``endpoints.toml`` base_url; headless fails closed).
Definition at line 324 of file _sandbox.py.
| bool oc_chat.tools._sandbox.is_importable_write_path | ( | "str | Path" | path | ) |
Return ``True`` if *path* is a ``.py`` under a harness/contract importable root (a write-then-import in-process ACE vector, F57). Profile-tracking by topology: under ``guarded``/``isolated`` the importable roots are outside the sandbox (G-1), so an in-sandbox write resolves under no root and returns ``False``; under ``permissive`` monorepo dogfooding it does overlap and returns ``True``.
Definition at line 359 of file _sandbox.py.
| bool oc_chat.tools._sandbox.is_path_inside_sandbox | ( | str | Path | path, |
| Path | None | root = None ) |
Return ``True`` if *path* resolves to a location inside *root*. If *root* is ``None`` (the default), the sandbox root is resolved at call time via :func:`_resolve_default_root`. Relative paths are interpreted relative to *root*. The path is resolved with ``strict=False`` so missing files do not raise.
Definition at line 133 of file _sandbox.py.
| bool oc_chat.tools._sandbox.is_protected_read_path | ( | str | Path | path | ) |
Return ``True`` if *path* is in the protected-read (secret) class. The deterministic, name/location-based secret class the read tools must refuse to read or list (M8 F46; M8.0 WU-2): ``.env`` / ``.env.*``, secret-bearing ``*.local.json`` (``*settings*`` / ``*secret*`` / ``*credential*`` / ...), ``agent_secrets.json``, the ``.netrc`` / ``.npmrc`` / ``.pypirc`` / ``.git-credentials`` credential stores, private-key material (``*.pem`` / ``*.key`` / ``*.pfx`` / ``*.p12`` and ``id_rsa`` / ``id_dsa`` / ``id_ecdsa`` / ``id_ed25519``), and anything under an ``.ssh`` / ``.gnupg`` / ``.aws`` credential directory. Matching is case-insensitive (Windows) and strips a Windows alternate-data-stream suffix AND trailing dots/spaces (``.env::$DATA`` / ``.env `` / ``.env.``) before classification. Content-tagged ``redaction_class=secret`` is the M8.3 metadata layer (F46), not this deterministic floor. The committed ``*settings*.json`` (no ``.local``) is a protected-WRITE concern, not protected-read.
Definition at line 220 of file _sandbox.py.
| bool oc_chat.tools._sandbox.is_protected_write_path | ( | "str | Path" | path | ) |
Return ``True`` if *path* is in the hard-deny protected-write class. The integrity / config-as-code / secret targets ``write_file`` (and other path sinks) must refuse BEFORE the sandbox clamp, regardless of profile (M8 §5.1.2 hard-deny + Appendix C G-4; M8.0 WU-5): anything under a ``.git`` directory or a ``.git*`` pointer / submodule-config file; the protected-read secret class (a secret must not be written either); ``*.pth`` site-path files; ``sitecustomize.py`` / ``usercustomize.py`` / ``conftest.py`` import/ collection hooks; ``.pre-commit-config.yaml``; the ``.oc_chat_clean_exit`` marker. Matching is case-insensitive (Windows) and strips a Windows alternate-data-stream suffix and trailing dots/spaces before classification. The softer human-confirm protected-write TIER (config-as-code incl. ``endpoints.toml``, + the importable tree) is :func:`is_human_confirm_write_path` / :func:`is_importable_write_path` (WU-8 / F70, §5.1.2). The F59 custody anchors and the contract ``canonical_json`` MAC preimage land with M8.3.
Definition at line 268 of file _sandbox.py.
| str oc_chat.tools._sandbox.redact_secrets | ( | str | text | ) |
Redact unambiguous secret tokens / keys from *text* (M8 F46/F51; WU-2). A deterministic, low-false-positive scrub applied to all secret-bearing tool and command output (``read_file`` / ``read_chunks`` content, ``run_command`` stdout/stderr) so a secret embedded in otherwise-readable content, or echoed by a command, does not exfiltrate. Matches well-known credential formats (PEM private keys, URL ``user:pass``, AWS / GitHub / Slack / Google keys, ``sk-`` / ``sk-ant-`` provider keys, JWTs, Bearer tokens); each match is replaced with a visible ``[REDACTED-SECRET]`` marker so the redaction is apparent rather than silently corrupting. Username / home-path / private-index *path* disclosure (the non-secret Tier-1 disclosure-safety, F51) is WU-3; content-class ``redaction_class=secret`` tagging is the M8.3 metadata layer.
Definition at line 463 of file _sandbox.py.
| Path oc_chat.tools._sandbox.require_inside_sandbox | ( | str | Path | path, |
| Path | None | root = None ) |
Resolve *path* and return it if inside *root*; raise otherwise. Returns the resolved absolute :class:`~pathlib.Path`. Raises :class:`SandboxViolation` when the resolved path is outside the sandbox root. Symlinks and ``..`` components are resolved before the check via ``Path.resolve(strict=False)``.
Definition at line 154 of file _sandbox.py.
| "dict[str, str]" oc_chat.tools._sandbox.scrubbed_env | ( | "dict[str, str] | None" | extra = None | ) |
Return a minimal subprocess environment (M8 F25/F72/F78/F79; WU-3c). ``os.environ`` filtered to :data:`_ENV_ALLOWLIST`, plus *extra* (caller-set vars such as ``PYTHONIOENCODING``/``TMPDIR``, applied last). Drops the attacker-poisonable families that would redirect a subprocess to a malicious index / proxy / cert or inject code, regardless of how they entered ``os.environ``. Matching is case-insensitive (Windows).
Definition at line 593 of file _sandbox.py.
| None oc_chat.tools._sandbox.set_session_sandbox_root_override | ( | str | Path | None | path | ) |
Set or clear the process-local session sandbox override.
Definition at line 102 of file _sandbox.py.
|
protected |
Definition at line 310 of file _sandbox.py.
|
protected |
Definition at line 394 of file _sandbox.py.
|
protected |
Definition at line 505 of file _sandbox.py.
|
protected |
Definition at line 395 of file _sandbox.py.
|
protected |
Definition at line 506 of file _sandbox.py.
|
protected |
Definition at line 386 of file _sandbox.py.
|
protected |
Definition at line 200 of file _sandbox.py.
|
protected |
Definition at line 190 of file _sandbox.py.
|
protected |
Definition at line 197 of file _sandbox.py.
|
protected |
Definition at line 258 of file _sandbox.py.
|
protected |
Definition at line 403 of file _sandbox.py.
|
protected |
Definition at line 383 of file _sandbox.py.
|
protected |
Definition at line 204 of file _sandbox.py.
|
protected |
Definition at line 99 of file _sandbox.py.
|
protected |
Definition at line 318 of file _sandbox.py.
| Path oc_chat.tools._sandbox.DEFAULT_SANDBOX_ROOT = _resolve_default_root() |
Definition at line 97 of file _sandbox.py.