fix(issue-26): review round 1 - no file key assigns bash's own variables; SKS_QUIESCENT_TRIES; long decimals
R1 (P1): a quoted export still ran code when the key was a bash special variable (RANDOM=a[$(cmd)] is evaluated arithmetically on assignment). envfile.RESERVED (bash's own variables, plus BASH_/COMP_/READLINE_ prefixes) is skipped with a line-numbered warning by both loaders; a test keeps it covering every variable a clean bash defines. The wrapper also hands SCRIPT_DIR/REPO_ROOT/TARGET_ENV to the parser as environment, so the file cannot replace the variables it has already set. R2 (P2): SKS_QUIESCENT_TRIES reached `for ((i < tries))` through a function argument; it is normalised at the call site (minimum 0). B5 now requires every integer-looking env default in the shell code to be normalised or listed in NOT_ARITHMETIC with where it goes, and exercises _pane_quiescent. R3 (P2): _positive_int strips leading zeros and rejects more than nine significant digits before int(), like mam_int; 5000-digit inputs no longer raise in the runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2826e709ae
commit
34241d7a65
6 files changed
+186
-16
No files matched your search
@@ -2782,7 +2782,7 @@ send_keys_safe() {
|
||||
fi
|
||||
|
||||
local _q_rc=0
|
||||
_pane_quiescent "$sess" "${SKS_QUIESCENT_TRIES:-20}" "${SKS_QUIESCENT_INTERVAL:-0.5}" || _q_rc=$?
|
||||
_pane_quiescent "$sess" "$(mam_int_knob SKS_QUIESCENT_TRIES 20 0)" "${SKS_QUIESCENT_INTERVAL:-0.5}" || _q_rc=$?
|
||||
if [ "$_q_rc" = "1" ]; then
|
||||
echo "send_keys_safe: pane never quiesced ($sess)" >&2
|
||||
return 1
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
Rules: blank lines and `#` comments are skipped; `KEY=VALUE` with the key and
|
||||
the value stripped; one matching pair of surrounding quotes is removed; no
|
||||
shell interpretation (`$(...)`, `${...}` and backticks stay literal). A key
|
||||
must match ``^[A-Za-z_][A-Za-z0-9_]*$``; a line that does not, or that has no
|
||||
`=`, is skipped with a warning naming the line (never the value — the file
|
||||
holds secrets). Variables already in the environment win over the file.
|
||||
must match ``^[A-Za-z_][A-Za-z0-9_]*$`` and not be one of bash's own variables
|
||||
(RESERVED); a line that breaks either, or that has no `=`, is skipped with a
|
||||
warning naming the line (never the value — the file holds secrets).
|
||||
Variables already in the environment win over the file.
|
||||
|
||||
Stdlib only: the delegate-job wrapper runs this file by path
|
||||
(``envfile.py --shell <path>``) and evals the ``export`` lines it prints, so
|
||||
@@ -19,6 +20,27 @@ from typing import List, MutableMapping, Tuple
|
||||
|
||||
_KEY = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
||||
|
||||
# Bash's own variables (bash(1) "Shell Variables"). Assigning some runs code
|
||||
# even from a quoted value (RANDOM/SECONDS/... are evaluated arithmetically,
|
||||
# so RANDOM='a[$(cmd)]' executes cmd), others steer the shell (IFS, PS4,
|
||||
# BASH_ENV, SHELLOPTS) or are readonly. A file key never assigns them - in
|
||||
# either loader. tests/test_issue26_envfile.py checks this covers every
|
||||
# variable a clean bash defines.
|
||||
RESERVED = frozenset("""
|
||||
BASH BASHOPTS BASHPID CDPATH CHILD_MAX COLUMNS COMPREPLY COPROC DIRSTACK EMACS ENV
|
||||
EPOCHREALTIME EPOCHSECONDS EUID EXECIGNORE FCEDIT FIGNORE FUNCNAME FUNCNEST GLOBIGNORE
|
||||
GLOBSORT GROUPS HISTCMD HISTCONTROL HISTFILE HISTFILESIZE HISTIGNORE HISTSIZE
|
||||
HISTTIMEFORMAT HOSTFILE HOSTNAME HOSTTYPE IFS IGNOREEOF INPUTRC INSIDE_EMACS LINENO LINES
|
||||
MACHTYPE MAIL MAILCHECK MAILPATH MAPFILE OLDPWD OPTARG OPTERR OPTIND OSTYPE PIPESTATUS
|
||||
POSIXLY_CORRECT PPID PROMPT_COMMAND PROMPT_DIRTRIM PS0 PS1 PS2 PS3 PS4 PWD RANDOM REPLY
|
||||
SECONDS SHELL SHELLOPTS SHLVL SRANDOM TIMEFORMAT TMOUT UID auto_resume histchars _
|
||||
""".split())
|
||||
RESERVED_PREFIXES = ("BASH_", "COMP_", "READLINE_")
|
||||
|
||||
|
||||
def is_reserved(key: str) -> bool:
|
||||
return key in RESERVED or key.startswith(RESERVED_PREFIXES)
|
||||
|
||||
|
||||
def parse(path: str) -> Tuple[List[Tuple[str, str]], List[str]]:
|
||||
"""Return ([(key, value)], [warning]) for the file at ``path``."""
|
||||
@@ -41,6 +63,9 @@ def parse(path: str) -> Tuple[List[Tuple[str, str]], List[str]]:
|
||||
if not _KEY.fullmatch(key):
|
||||
warnings.append(f"{path}:{n}: skipped (invalid key)")
|
||||
continue
|
||||
if is_reserved(key):
|
||||
warnings.append(f"{path}:{n}: skipped (reserved shell variable {key})")
|
||||
continue
|
||||
val = val.strip()
|
||||
if len(val) >= 2 and val[0] == val[-1] and val[0] in "\"'":
|
||||
val = val[1:-1]
|
||||
|
||||
@@ -74,8 +74,11 @@ def _positive_int(text: Optional[str], minimum: int = 1) -> Optional[int]:
|
||||
s = (text or "").strip(" \t\n\r\v\f")
|
||||
if not s or not s.isascii() or not s.isdigit():
|
||||
return None
|
||||
s = s.lstrip("0") or "0"
|
||||
if len(s) > 9: # like mam_int: bounded before int(), which refuses > 4300 digits
|
||||
return None
|
||||
n = int(s)
|
||||
return n if minimum <= n <= 999_999_999 else None
|
||||
return n if n >= minimum else None
|
||||
|
||||
class LoopRunner:
|
||||
"""Orchestrates planning, code implementation, and multi-agent peer reviews."""
|
||||
|
||||
@@ -35,9 +35,12 @@ if [[ -z "$TARGET_ENV" ]]; then
|
||||
fi
|
||||
|
||||
# Issue #26: parsed by envfile.py (the Python loader's rules; environment wins),
|
||||
# never sourced. Values come back as shlex-quoted exports over the pipe.
|
||||
# never sourced. Values come back as shlex-quoted exports over the pipe. The
|
||||
# variables this script has already set are handed over as environment, so
|
||||
# the file cannot replace them either.
|
||||
if [[ -n "$TARGET_ENV" && -f "$TARGET_ENV" ]]; then
|
||||
_env_exports="$("${DELEGATE_JOB_PYTHON:-python3}" -I "$SCRIPT_DIR/../lib_py/delegation/envfile.py" --shell "$TARGET_ENV")" \
|
||||
_env_exports="$(SCRIPT_DIR="$SCRIPT_DIR" REPO_ROOT="$REPO_ROOT" TARGET_ENV="$TARGET_ENV" \
|
||||
"${DELEGATE_JOB_PYTHON:-python3}" -I "$SCRIPT_DIR/../lib_py/delegation/envfile.py" --shell "$TARGET_ENV")" \
|
||||
|| { echo "ERROR: cannot read $TARGET_ENV" >&2; exit 1; }
|
||||
eval "$_env_exports"; unset _env_exports
|
||||
fi
|
||||
|
||||
Reference in new issue
Block a user