Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2538-agent-trust-detect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **security:** detect agent hijacking via planted `AGENTS.md`/`CLAUDE.md` (download dirs, `/tmp` and other shared ancestor roots), endpoint overrides, unpinned `npx` MCP servers and the known-bad npm `function-color` — shared collector `claude/hooks/lib/agent_trust.py`, new `agent trust` rows in `ccc-security-audit.sh`, a baseline-diff fleet sweep `scripts/agent-trust-audit-fleet.sh` and its hub-only weekly cron installer (`--nodes` required; no roster in the repo, #1451) (detection only; TM-1306 unchanged) (#2538).
497 changes: 497 additions & 0 deletions claude/hooks/lib/agent_trust.py

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions docs/security-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,55 @@ The fleet matrix is read-only. It does not SSH, change permissions, restart serv
## Reporting boundary

Report counts, statuses, file modes, safe reason tags, and remediation class. Do not include raw matched lines if they may contain credentials or private memory.

## Agent trust surface (#2538)

Background: a web page can silently drop an `AGENTS.md` / `CLAUDE.md` into
`~/Downloads`; a Codex or Claude Code session started there follows it as
instructions (forever.security, 2026-10-22). Reproduced payloads added an MCP
server to `~/.codex/config.toml` (malicious npm `function-color`) and set
`ANTHROPIC_BASE_URL` in `.claude/settings.json` (OAuth token leak). The fleet
variant: A2A analysis sessions run under `/tmp/a2a-analysis-*`, and Claude Code
loads `CLAUDE.md` from every ancestor directory, so one `/tmp/CLAUDE.md` would
reach every worker session on a node.

`claude/hooks/lib/agent_trust.py` is the shared, stdlib-only collector. It is
**detection only** — TM-1306 keeps blocking guards and `permissions.deny` out
of the harness. Output is hosts, paths and hashes; never file contents or
secret values.

| Severity | Finding | Meaning |
|---|---|---|
| 위험 / danger | `download-agent-file` | agent instruction/config file in a download dir, outside git |
| 위험 / danger | `ancestor-agent-file` | `AGENTS.md`/`CLAUDE.md` directly in `/`, `/tmp`, `/var/tmp`, `$PREFIX/tmp`, or a home parent |
| 위험 / danger | `known-bad-npm` | global `node_modules/function-color` |
| 경고 / warn | `endpoint-override` | Claude `env.*BASE_URL` / Codex `*base_url` host not in the allowlist (`CCC_AGENT_TRUST_ENDPOINT_ALLOW`, comma-separated hosts) |
| 경고 / warn | `unpinned-npx-mcp` | MCP server launched as `npx -y <pkg>` without an exact version |
| 경고 / warn | `home-agent-file` | unmanaged `AGENTS.md`/`CLAUDE.md` in a home dir (loaded by every bridge session) |
| 경고 / warn | `ancestor-agent-config`, `session-in-downloads`, download file inside a git checkout | review |

```bash
python3 claude/hooks/lib/agent_trust.py # human summary
python3 claude/hooks/lib/agent_trust.py --json # ccc.agent-trust.v1
bash scripts/ccc-security-audit.sh # includes "agent trust" rows
```

### Weekly fleet sweep (hub only)

`scripts/agent-trust-audit-fleet.sh` pipes the collector to every node over ssh
(`python3 - --json`, independent of the remote checkout's age) and diffs each
node's `signature` (endpoint hosts, MCP servers, home instruction files,
findings) against an accepted baseline under
`~/.claude/state/agent-trust-audit/`. Verdicts: `OK`, `DANGER` (always fails,
even when accepted), `NEW` (fails), `GONE`, `UNBASELINED`, `UNREACHABLE`,
`ERROR`.

```bash
CCC_FLEET_NODES="<ssh aliases>" bash scripts/agent-trust-audit-fleet.sh # review
CCC_FLEET_NODES="<ssh aliases>" bash scripts/agent-trust-audit-fleet.sh --accept-baseline
bash scripts/install-agent-trust-audit-cron.sh --apply --nodes "<ssh aliases>" # Mon 06:40, hub only
```

If a finding is real: do not run agents in that directory, delete the planted
file, review `~/.codex/config.toml` and Claude settings for MCP/endpoint
changes, and rotate the agent OAuth/API tokens.
140 changes: 140 additions & 0 deletions scripts/agent-trust-audit-fleet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env bash
# agent-trust-audit-fleet — periodic fleet agent trust-surface check (ccc-node#2538).
#
# Pipes claude/hooks/lib/agent_trust.py to every node over ssh
# (`python3 - --json`, or locally for CCC_FLEET_SELF), so the check does not
# depend on the age of the remote checkout. Each node's JSON is stored under
# the state dir and its `signature` (endpoint hosts, MCP server names/targets,
# home-level instruction files, warn/danger findings) is compared with the
# node's ACCEPTED BASELINE.
#
# Verdicts per node:
# DANGER <items> IOC-class finding (planted AGENTS.md/CLAUDE.md in a download
# dir or a shared ancestor root such as /tmp, known-bad npm).
# Always fails, even when present in the baseline.
# NEW <items> something appeared since the baseline (e.g. a new MCP
# server or endpoint override) — fails.
# GONE <items> something disappeared — informational.
# OK | UNBASELINED | UNREACHABLE (fails) | ERROR (collector crashed; fails)
# Exit 1 on DANGER/NEW/UNREACHABLE/ERROR so an agent-cron
# `telegram-owner-on-failure` payload notifies the owner; else 0.
#
# Layout (default $CCC_STATE_DIR/agent-trust-audit):
# current/<node>.json latest collection
# baseline/<node>.json accepted baseline
# runs/<utc-ts>.txt one verdict line per node per run (bounded)
#
# Read-only fleet-wide except the state dir on the hub. The collector prints
# hosts, paths and hashes only — never file contents or secret values.
# Detection only: TM-1306 keeps blocking guards out of the harness.
set -uo pipefail

SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COLLECTOR="${CCC_AGENT_TRUST_COLLECTOR:-$SELF_DIR/../claude/hooks/lib/agent_trust.py}"
# No built-in roster (#1451: node names must not spread). The hub's cron line
# carries the list (install-agent-trust-audit-cron.sh --nodes).
NODES="${CCC_FLEET_NODES:-}"
SSH_BIN="${CCC_FLEET_SSH:-ssh}"
SELF="${CCC_FLEET_SELF:-$(hostname -s 2>/dev/null || echo _none_)}"
STATE_DIR="${CCC_STATE_DIR:-${HOME:-/root}/.claude/state}"
AUDIT_DIR="${CCC_AGENT_TRUST_AUDIT_DIR:-$STATE_DIR/agent-trust-audit}"
SSH_TIMEOUT="${CCC_FLEET_SSH_TIMEOUT:-90}"
KEEP_RUNS="${CCC_AGENT_TRUST_AUDIT_KEEP_RUNS:-30}"

ACCEPT=0
ACCEPT_NODES=""
QUIET=0
for arg in "$@"; do
case "$arg" in
--accept-baseline) ACCEPT=1 ;;
--accept-baseline=*) ACCEPT=1; ACCEPT_NODES="${arg#--accept-baseline=}" ;;
--quiet) QUIET=1 ;;
-h|--help)
cat <<'EOF'
Usage: agent-trust-audit-fleet.sh [--accept-baseline[=node1,node2]] [--quiet]
Collect the agent trust surface (planted AGENTS.md/CLAUDE.md, endpoint
overrides, MCP servers, known-bad npm) from every fleet node and compare it
with each node's accepted baseline. Verdicts: OK | DANGER | NEW | GONE |
UNBASELINED | UNREACHABLE | ERROR. Exit 1 on DANGER/NEW/UNREACHABLE/ERROR.
--accept-baseline freezes the current collection (all nodes or the list);
DANGER findings keep failing even after acceptance.
Env: CCC_FLEET_NODES (required), CCC_FLEET_SSH, CCC_FLEET_SELF, CCC_STATE_DIR /
CCC_AGENT_TRUST_AUDIT_DIR, CCC_FLEET_SSH_TIMEOUT, CCC_AGENT_TRUST_COLLECTOR.
EOF
exit 0
;;
*) echo "unknown arg: $arg" >&2; exit 2 ;;
esac
done

[ -n "$NODES" ] || { echo "agent-trust-audit-fleet: set CCC_FLEET_NODES (space-separated ssh aliases)" >&2; exit 2; }
[ -r "$COLLECTOR" ] || { echo "agent-trust-audit-fleet: collector not found: $COLLECTOR" >&2; exit 2; }
command -v jq >/dev/null 2>&1 || { echo "agent-trust-audit-fleet: jq is required" >&2; exit 2; }
mkdir -p "$AUDIT_DIR/current" "$AUDIT_DIR/baseline" "$AUDIT_DIR/runs" 2>/dev/null || {
echo "agent-trust-audit-fleet: cannot create $AUDIT_DIR" >&2; exit 2; }

collect() { # <node> -> current/<node>.json ; 0 ok, 1 unreachable, 2 collector error
local node="$1" out="$AUDIT_DIR/current/$1.json" tmp raw
tmp="$(mktemp "$out.XXXXXX")" || return 1
if [ "$node" = "$SELF" ]; then
raw="$(python3 "$COLLECTOR" --json 2>/dev/null)"
else
raw="$(timeout "$SSH_TIMEOUT" "$SSH_BIN" -o BatchMode=yes -o ConnectTimeout=10 "$node" "python3 - --json" <"$COLLECTOR" 2>/dev/null)"
fi
if ! printf '%s' "$raw" | jq -e '.schema == "ccc.agent-trust.v1"' >/dev/null 2>&1; then
rm -f "$tmp"; return 1
fi
if printf '%s' "$raw" | jq -e 'has("error")' >/dev/null 2>&1; then
rm -f "$tmp"; return 2
fi
printf '%s\n' "$raw" > "$tmp" && mv "$tmp" "$out"
}

signature() { jq -r '.signature[]?' "$1" 2>/dev/null | LC_ALL=C sort -u; }
dangers() { jq -r '.findings[]? | select(.severity == "danger") | "\(.kind) \(.id)"' "$1" 2>/dev/null | paste -sd ';' -; }

run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$"
run_file="$AUDIT_DIR/runs/$run_id.txt"
fail=0
for node in $NODES; do
cur="$AUDIT_DIR/current/$node.json"
base="$AUDIT_DIR/baseline/$node.json"
collect "$node"; rc=$?
if [ "$rc" = 1 ]; then
verdict="UNREACHABLE $node"; fail=1
elif [ "$rc" = 2 ]; then
verdict="ERROR $node (collector crashed; run python3 agent_trust.py on the node)"; fail=1
else
danger="$(dangers "$cur")"
accept_this=0
if [ "$ACCEPT" = 1 ]; then
if [ -z "$ACCEPT_NODES" ]; then accept_this=1
else case ",$ACCEPT_NODES," in *",$node,"*) accept_this=1 ;; esac; fi
fi
if [ "$accept_this" = 1 ]; then
cp "$cur" "$base"
verdict="BASELINE-ACCEPTED $node ($(signature "$cur" | wc -l | tr -d ' ') items)"
elif [ ! -f "$base" ]; then
verdict="UNBASELINED $node ($(signature "$cur" | wc -l | tr -d ' ') items; review, then --accept-baseline=$node)"
else
new="$(comm -13 <(signature "$base") <(signature "$cur") | cut -f2- | paste -sd ';' -)"
gone="$(comm -23 <(signature "$base") <(signature "$cur") | cut -f2- | paste -sd ';' -)"
if [ -n "$new" ]; then
verdict="NEW $node: $new"; fail=1
[ -n "$gone" ] && verdict="$verdict | GONE: $gone"
elif [ -n "$gone" ]; then
verdict="GONE $node: $gone"
else
verdict="OK $node"
fi
fi
if [ -n "$danger" ]; then
verdict="DANGER $node: $danger | $verdict"; fail=1
fi
fi
printf '%s\n' "$verdict" >> "$run_file"
[ "$QUIET" = 1 ] || printf '%s\n' "$verdict"
done

ls -1t "$AUDIT_DIR/runs" 2>/dev/null | tail -n +"$((KEEP_RUNS + 1))" | while read -r old; do rm -f "$AUDIT_DIR/runs/$old"; done
exit "$fail"
87 changes: 87 additions & 0 deletions scripts/agent-trust-audit-fleet.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Hermetic tests for agent-trust-audit-fleet.sh (#2538) — ssh is stubbed; each
# "node" answers with canned ccc.agent-trust.v1 JSON from $TMP/reply/<node>.
set -uo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
FLEET="$ROOT/scripts/agent-trust-audit-fleet.sh"
# shellcheck source=claude/hooks/lib/test-stub.sh
. "$ROOT/claude/hooks/lib/test-stub.sh"
ccc_test_reset_hook_env
TMP="$(ccc_test_tmpdir)" || exit 1
trap 'rm -rf "$TMP"' EXIT

pass=0; fail=0
ok() { if eval "$2"; then pass=$((pass+1)); else fail=$((fail+1)); echo "FAIL: $1"; fi; }

mkdir -p "$TMP/reply" "$TMP/bin" "$TMP/state"
write_exec_stub "$TMP/bin/ssh" <<EOF
node=""
for a in "\$@"; do case "\$a" in -*|BatchMode=yes|ConnectTimeout=10|"python3 - --json") ;; *) node="\$a" ;; esac; done
cat >/dev/null
f="$TMP/reply/\$node"
[ -f "\$f" ] || exit 255
cat "\$f"
EOF

doc() { # <extra-signature-json-items> <findings-json>
cat <<EOF
{"schema":"ccc.agent-trust.v1","host":"x","counts":{"danger":0,"warn":0,"info":0},
"findings":$2,"inventory":{},
"signature":["claude-mcp\tfamily-ops -> python3"$1]}
EOF
}
doc "" "[]" > "$TMP/reply/alpha"
doc "" "[]" > "$TMP/reply/beta"

export CCC_FLEET_NODES="alpha beta" CCC_FLEET_SSH="$TMP/bin/ssh" CCC_FLEET_SELF=_none_ \
CCC_STATE_DIR="$TMP/state" CCC_FLEET_SSH_TIMEOUT=10 PATH="$TMP/bin:$PATH"

out="$(bash "$FLEET" 2>&1)"; rc=$?
ok "unbaselined nodes exit 0" '[ "$rc" = 0 ] && grep -q "^UNBASELINED alpha" <<<"$out" && grep -q "^UNBASELINED beta" <<<"$out"'
ok "current collection stored" 'jq -e ".schema" "$TMP/state/agent-trust-audit/current/alpha.json" >/dev/null'

out="$(bash "$FLEET" --accept-baseline 2>&1)"; rc=$?
ok "accept-baseline (all) exits 0" '[ "$rc" = 0 ] && grep -q "^BASELINE-ACCEPTED alpha" <<<"$out" && grep -q "^BASELINE-ACCEPTED beta" <<<"$out"'

out="$(bash "$FLEET" 2>&1)"; rc=$?
ok "unchanged nodes are OK" '[ "$rc" = 0 ] && grep -q "^OK alpha" <<<"$out" && grep -q "^OK beta" <<<"$out"'

# A new MCP server + endpoint override appear on alpha → NEW, exit 1, items named.
doc ',"codex-mcp\tevil -> npx","endpoint\tclaude:~/.claude/settings.json:ANTHROPIC_BASE_URL=https://evil.example"' "[]" > "$TMP/reply/alpha"
out="$(bash "$FLEET" 2>&1)"; rc=$?
ok "new MCP / endpoint is NEW and fails" '[ "$rc" = 1 ] && grep -q "^NEW alpha: .*evil -> npx" <<<"$out" && grep -q "ANTHROPIC_BASE_URL=https://evil.example" <<<"$out"'
ok "other node stays OK" 'grep -q "^OK beta" <<<"$out"'

# A danger finding fails even after it is accepted into the baseline.
doc "" '[{"severity":"danger","kind":"ancestor-agent-file","id":"/tmp/CLAUDE.md","detail":"sha=abc"}]' > "$TMP/reply/beta"
bash "$FLEET" --accept-baseline=beta >/dev/null 2>&1
out="$(bash "$FLEET" 2>&1)"; rc=$?
ok "accepted danger still fails as DANGER" '[ "$rc" = 1 ] && grep -q "^DANGER beta: ancestor-agent-file /tmp/CLAUDE.md" <<<"$out"'

# Unreachable node fails; collector error fails.
rm -f "$TMP/reply/alpha"
printf '{"schema":"ccc.agent-trust.v1","host":"b","error":"OSError"}\n' > "$TMP/reply/beta"
out="$(bash "$FLEET" 2>&1)"; rc=$?
ok "unreachable and collector error both fail" '[ "$rc" = 1 ] && grep -q "^UNREACHABLE alpha" <<<"$out" && grep -q "^ERROR beta" <<<"$out"'

ok "run history bounded and written" '[ "$(ls "$TMP/state/agent-trust-audit/runs" | wc -l)" -ge 1 ]'

# Local (self) path runs the real collector against an empty fixture.
mkdir -p "$TMP/home" "$TMP/roots"
out="$(CCC_FLEET_NODES=me CCC_FLEET_SELF=me CCC_AGENT_TRUST_HOMES="$TMP/home" CCC_AGENT_TRUST_TMP_ROOTS="$TMP/roots" \
CCC_AGENT_TRUST_ANCESTOR_ROOTS="$TMP/roots" CCC_AGENT_TRUST_NPM_ROOTS="" bash "$FLEET" 2>&1)"; rc=$?
ok "self node collected locally with the real collector" '[ "$rc" = 0 ] && grep -q "^UNBASELINED me" <<<"$out"'
printf 'x\n' > "$TMP/roots/AGENTS.md"
# shellcheck disable=SC2034 # out/rc are read via eval inside ok()
out="$(CCC_FLEET_NODES=me CCC_FLEET_SELF=me CCC_AGENT_TRUST_HOMES="$TMP/home" CCC_AGENT_TRUST_TMP_ROOTS="$TMP/roots" \
CCC_AGENT_TRUST_ANCESTOR_ROOTS="$TMP/roots" CCC_AGENT_TRUST_NPM_ROOTS="" bash "$FLEET" 2>&1)"
# shellcheck disable=SC2034 # read via eval inside ok()
rc=$?
ok "real collector flags a planted ancestor AGENTS.md as DANGER" '[ "$rc" = 1 ] && grep -q "^DANGER me: ancestor-agent-file $TMP/roots/AGENTS.md" <<<"$out"'

ok "missing CCC_FLEET_NODES exits 2 (no built-in roster)" 'CCC_FLEET_NODES="" bash "$FLEET" >/dev/null 2>&1; [ "$?" = 2 ]'
ok "bad arg exits 2" 'bash "$FLEET" --nope >/dev/null 2>&1; [ "$?" = 2 ]'

echo "----"; echo "PASS=$pass FAIL=$fail"
[ "$fail" = 0 ]
14 changes: 9 additions & 5 deletions scripts/ccc-doctor.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,7 @@ cdrepo="$TMP/cron-drift-repo"
mkdir -p "$cdrepo/scripts/lib" "$cdrepo/.claude"
cp "$ROOT/scripts/lib/installer-gen-stamp.sh" "$cdrepo/scripts/lib/"
cp "$ROOT/scripts/lib/installer-cron-common.sh" "$cdrepo/scripts/lib/"
for s in install-memory-refresh-cron install-pr-status-poll-cron install-skill-autosave-cron install-cost-ledger-cron install-fleet-skills-sync-cron install-tunnel-audit-cron install-timed-test-deadline-scan-cron install-timed-followup-scan-cron install-nunchi; do
for s in install-memory-refresh-cron install-pr-status-poll-cron install-skill-autosave-cron install-cost-ledger-cron install-fleet-skills-sync-cron install-tunnel-audit-cron install-agent-trust-audit-cron install-timed-test-deadline-scan-cron install-timed-followup-scan-cron install-nunchi; do
cp "$ROOT/scripts/$s.sh" "$cdrepo/scripts/"
done
# shellcheck source=/dev/null
Expand All @@ -1134,6 +1134,7 @@ gen_sa="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-skill-autosave-c
gen_cl="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-cost-ledger-cron.sh")"
gen_fs="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-fleet-skills-sync-cron.sh")"
gen_ta="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-tunnel-audit-cron.sh")"
gen_at="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-agent-trust-audit-cron.sh")"
gen_nu="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-nunchi.sh")"
gen_td="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-timed-test-deadline-scan-cron.sh")"
gen_tf="$(ccc_installer_gen_stamp_auto "$cdrepo/scripts/install-timed-followup-scan-cron.sh")"
Expand Down Expand Up @@ -1162,10 +1163,10 @@ run_cd() { # <cron-text> [repo-dir]
}

cd_out="$(run_cd "")"
ok "empty crontab: eleven opt-in rows, all 정상" \
'jq -e "[.rows[] | select(.klass != \"정상\")] | length == 0" <<<"$cd_out" >/dev/null && [ "$(jq ".rows | length" <<<"$cd_out")" = 11 ]'
ok "empty crontab: rows are the eleven known markers" \
'jq -e "[.rows[].item] == [\"cron gen memory-refresh\", \"cron gen pr-status-poll\", \"cron gen skill-autosave\", \"cron gen cost-ledger\", \"cron gen fleet-skills-sync\", \"cron gen tunnel-audit\", \"cron gen t2-starvation-observe\", \"cron gen nunchi\", \"cron gen timed-test-deadline-scan\", \"cron gen timed-followup-scan\", \"cron gen skill-promotion-results\"]" <<<"$cd_out" >/dev/null'
ok "empty crontab: twelve opt-in rows, all 정상" \
'jq -e "[.rows[] | select(.klass != \"정상\")] | length == 0" <<<"$cd_out" >/dev/null && [ "$(jq ".rows | length" <<<"$cd_out")" = 12 ]'
ok "empty crontab: rows are the twelve known markers" \
'jq -e "[.rows[].item] == [\"cron gen memory-refresh\", \"cron gen pr-status-poll\", \"cron gen skill-autosave\", \"cron gen cost-ledger\", \"cron gen fleet-skills-sync\", \"cron gen tunnel-audit\", \"cron gen agent-trust-audit\", \"cron gen t2-starvation-observe\", \"cron gen nunchi\", \"cron gen timed-test-deadline-scan\", \"cron gen timed-followup-scan\", \"cron gen skill-promotion-results\"]" <<<"$cd_out" >/dev/null'

full_cron="# ccc-node:memory-refresh:begin
*/30 * * * * bash -lc 'x' >> /l 2>&1 # ccc-node:memory-refresh gen=$gen_mr
Expand All @@ -1187,6 +1188,9 @@ CRON_TZ=Etc/UTC
# ccc-node:tunnel-audit:begin
20 6 * * 1 bash -lc 'x' >> /l 2>&1 # ccc-node:tunnel-audit gen=$gen_ta
# ccc-node:tunnel-audit:end
# ccc-node:agent-trust-audit:begin
40 6 * * 1 bash -lc 'x' >> /l 2>&1 # ccc-node:agent-trust-audit gen=$gen_at
# ccc-node:agent-trust-audit:end
*/10 * * * * bash /h/ingest-cron.sh >> /l 2>&1 # nunchi:#816 gen=$gen_nu
17 * * * * bash /h/mempalace-refresh.sh codex /s >> /l 2>&1 # nunchi:#816 gen=$gen_nu
7 8 * * 1 bash /h/bench.sh >> /l 2>&1 # nunchi:#816 gen=$gen_nu"
Expand Down
Loading
Loading