Skip to content

Commit 7470bf4

Browse files
author
Foundups Agent
committed
fix(reddog): confine runtime artifacts and redact daemon telemetry
Worker-Lane: REDDOG_RUNTIME_ARTIFACT_PATH_DAEMON_REDACTION_PHASE1 Slice: REDDOG_RUNTIME_ARTIFACT_PATH_DAEMON_REDACTION_PHASE1
1 parent ee42d40 commit 7470bf4

18 files changed

Lines changed: 1641 additions & 97 deletions

modules/communication/moltbot_bridge/ModLog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ signer was started and no production draft PR was created by this slice; the
3939
live proof remains blocked until WSL receives the required outside-repo
4040
authority/signer artifacts and an already-running isolated signer.
4141

42+
## 2026-07-18: REDDOG_RUNTIME_ARTIFACT_PATH_DAEMON_REDACTION_PHASE1
43+
44+
- Confined resident runtime paths outside the repository and beneath the
45+
configured runtime root.
46+
- Hardened resident control receipts with bounded fields, existing-chain
47+
validation, locked append, descriptor revalidation, and fsync.
48+
- Redacted DAEmon fix details before supervisor action reporting.
49+
4250
## 2026-07-18: REDDOG_HOLOINDEX_QUERY_OWNER_BOUNDARY_POC_PHASE1
4351

4452
**WSP Protocol**: WSP 00, 05, 06, 15, 22, 50, 62, 87, 96, 97

modules/communication/moltbot_bridge/config/openclaw_integration_manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@
204204
"env_keys": [
205205
"OPENCLAW_SELF_AUDIT_ENABLED",
206206
"OPENCLAW_SELF_AUDIT_AUTO_FIX",
207-
"OPENCLAW_SELF_AUDIT_INTERVAL_SEC"
207+
"OPENCLAW_SELF_AUDIT_INTERVAL_SEC",
208+
"OPENCLAW_SELF_AUDIT_RUNTIME_ROOT"
208209
],
209210
"secret_keys": [],
210211
"external_service": "local_service",

modules/communication/moltbot_bridge/src/openclaw_supervisor.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
from pathlib import Path
3636
from typing import Any, Callable, Deque, Dict, List, Mapping, Optional, Sequence
3737

38+
from modules.infrastructure.shared_utilities.runtime_artifact_safety import (
39+
redact_runtime_text,
40+
redact_runtime_value,
41+
)
42+
3843
logger = logging.getLogger(__name__)
3944

4045
READONLY_AUDIT_OPENCLAW_CLAIM_ACCEPT = "READONLY_AUDIT_OPENCLAW_CLAIM_ACCEPT"
@@ -1357,7 +1362,9 @@ def __init__(
13571362
self._stop_event = threading.Event()
13581363
self._broker = broker
13591364
self._observer = observer
1360-
self._action_reporter = action_reporter or self._build_daemon_reporter()
1365+
self._action_reporter = self._redacting_reporter(
1366+
action_reporter or self._build_daemon_reporter()
1367+
)
13611368
self._self_audit_factory = self_audit_factory
13621369
self._self_audit_loop: Any | None = None
13631370
self._event_cursor = 0
@@ -1571,19 +1578,32 @@ def run_cycle(self, parent_context=None) -> Dict[str, Any]:
15711578
self._remember(observation, plan, action_result, verify)
15721579
self._transition(SupervisorState.IDLE_WATCH, "cycle_complete")
15731580

1574-
self.last_cycle = {
1581+
self.last_cycle = redact_runtime_value({
15751582
"state": self.current_state.value,
15761583
"plan": plan,
15771584
"action_result": action_result,
15781585
"verify": verify,
15791586
"observation": observation,
1580-
}
1587+
})
15811588
return self.last_cycle
15821589

15831590
# ------------------------------------------------------------------ #
15841591
# Infrastructure helpers #
15851592
# ------------------------------------------------------------------ #
15861593

1594+
@staticmethod
1595+
def _redacting_reporter(
1596+
reporter: Callable[[str, str, Dict[str, Any]], None],
1597+
) -> Callable[[str, str, Dict[str, Any]], None]:
1598+
def safe_reporter(action_type: str, result: str, details: Dict[str, Any]) -> None:
1599+
reporter(
1600+
redact_runtime_text(action_type, max_chars=120).text,
1601+
redact_runtime_text(result, max_chars=200).text,
1602+
redact_runtime_value(details),
1603+
)
1604+
1605+
return safe_reporter
1606+
15871607
def _build_daemon_reporter(self) -> Callable[[str, str, Dict[str, Any]], None]:
15881608
from modules.infrastructure.dae_daemon.src.dae_daemon import get_central_daemon
15891609
from modules.infrastructure.dae_daemon.src.schemas import DAEEventType
@@ -2211,22 +2231,30 @@ def _execute(self, plan: Dict[str, Any]) -> Dict[str, Any]:
22112231

22122232
elif plan["action"] == "execute_self_audit_fix":
22132233
recommended_fix = plan.get("recommended_fix", "")
2234+
safe_plan = dict(plan)
2235+
safe_plan["event_signature"] = redact_runtime_text(
2236+
safe_plan.get("event_signature"), max_chars=240
2237+
).text
22142238
result: Dict[str, Any] = {"ok": False, "error": "no_audit_loop"}
22152239
if self._self_audit_loop and hasattr(self._self_audit_loop, "_apply_policy_fix"):
22162240
try:
22172241
success, detail = self._self_audit_loop._apply_policy_fix(recommended_fix)
22182242
result = {
22192243
"ok": success,
22202244
"status": "applied" if success else "fix_failed",
2221-
"detail": str(detail)[:500],
2245+
"detail": redact_runtime_text(detail, max_chars=500).text,
22222246
}
22232247
except Exception as exc:
2224-
result = {"ok": False, "status": "fix_error", "error": str(exc)[:500]}
2248+
result = {
2249+
"ok": False,
2250+
"status": "fix_error",
2251+
"error": redact_runtime_text(exc, max_chars=500).text,
2252+
}
22252253

22262254
self._action_reporter(
22272255
"supervisor_execute",
22282256
result.get("status", result.get("error", "unknown")),
2229-
{"plan": plan, "result": result},
2257+
{"plan": safe_plan, "result": result},
22302258
)
22312259
if _rt_start is not None:
22322260
try:
@@ -2427,16 +2455,20 @@ def _remember(
24272455
if verify.get("ok"):
24282456
self.metrics.tasks_succeeded += 1
24292457

2458+
safe_plan = redact_runtime_value(plan_or_triage)
2459+
safe_action_result = redact_runtime_value(action_result)
2460+
safe_verify = redact_runtime_value(verify)
2461+
24302462
# Report to daemon
24312463
self._action_reporter(
24322464
"supervisor_cycle",
24332465
"recorded",
24342466
{
24352467
"state": self.current_state.value,
24362468
"reason": self.last_reason,
2437-
"plan": plan_or_triage,
2438-
"action_result": action_result,
2439-
"verify": verify,
2469+
"plan": safe_plan,
2470+
"action_result": safe_action_result,
2471+
"verify": safe_verify,
24402472
"git": observation.get("git", {}),
24412473
"restart_budget": observation.get("restart_budget", {}),
24422474
"openclaw_follow": observation.get("openclaw_follow", {}),
@@ -2448,21 +2480,21 @@ def _remember(
24482480
try:
24492481
from modules.infrastructure.wre_core.src.pattern_memory import SkillOutcome
24502482

2451-
skill_name = plan_or_triage.get("action", "unknown")
2483+
skill_name = safe_plan.get("action", "unknown")
24522484
fidelity = float(verify.get("fidelity", 0.85))
24532485
outcome = SkillOutcome(
24542486
execution_id=f"supervisor_{uuid.uuid4().hex[:12]}",
24552487
skill_name=skill_name,
24562488
agent="openclaw_supervisor",
24572489
timestamp=datetime.now().isoformat(),
2458-
input_context=json.dumps(plan_or_triage, default=str)[:2000],
2459-
output_result=json.dumps(action_result, default=str)[:2000],
2490+
input_context=json.dumps(safe_plan, default=str)[:2000],
2491+
output_result=json.dumps(safe_action_result, default=str)[:2000],
24602492
success=bool(verify.get("ok", False)),
24612493
pattern_fidelity=fidelity,
24622494
outcome_quality=1.0 if verify.get("ok") else 0.0,
24632495
execution_time_ms=int(action_result.get("execution_time_ms", 0)),
24642496
step_count=1,
2465-
notes=f"Supervisor cycle: {plan_or_triage.get('reason', '')}",
2497+
notes=f"Supervisor cycle: {safe_plan.get('reason', '')}",
24662498
)
24672499
self._pattern_memory.store_outcome(outcome)
24682500
logger.debug(
@@ -2477,7 +2509,7 @@ def _remember(
24772509
self._event_cursor = int(follow.get("next_cursor", self._event_cursor) or self._event_cursor)
24782510

24792511
# Gateway Continuity Layer: Record breadcrumb with continuity metadata
2480-
self._record_continuity_breadcrumb(plan_or_triage, action_result, verify)
2512+
self._record_continuity_breadcrumb(safe_plan, safe_action_result, safe_verify)
24812513

24822514
def _create_continuity_context(self, cycle_id: str, parent_context=None) -> Any:
24832515
"""Create continuity context for a supervisor cycle.

modules/communication/moltbot_bridge/src/reddog_resident_control_loop_receipt_store.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
from pathlib import Path
1717
from typing import Any, Mapping
1818

19+
from modules.infrastructure.shared_utilities.runtime_artifact_safety import (
20+
secure_append_runtime_text,
21+
)
22+
1923

2024
CONTROL_LOOP_RECEIPT_SCHEMA_VERSION = "reddog_resident_control_loop_receipt.v1"
2125

@@ -59,13 +63,15 @@ def build_resident_control_loop_receipt(
5963
payload = {
6064
"schema_version": CONTROL_LOOP_RECEIPT_SCHEMA_VERSION,
6165
"accepted": bool(result.get("accepted")),
62-
"status": str(result.get("status") or ""),
66+
"status": _bounded_text(result.get("status"), 80),
6367
"rounds": _int(result.get("rounds")),
6468
"serial_progress": _int(result.get("serial_progress")),
6569
"claim_progress": _int(result.get("claim_progress")),
66-
"receipt_ids": _string_tuple(result.get("receipt_ids")),
67-
"rejection_reasons": _string_tuple(result.get("rejection_reasons")),
68-
"created_at": str(created_at or ""),
70+
"receipt_ids": _string_tuple(result.get("receipt_ids"), max_chars=256),
71+
"rejection_reasons": _string_tuple(
72+
result.get("rejection_reasons"), max_chars=512
73+
),
74+
"created_at": _bounded_text(created_at, 80),
6975
"repo_root_digest": _digest(str(Path(repo_root).resolve())),
7076
"control_lock_acquired": result.get("control_lock_acquired") is True,
7177
"no_authority_issued": True,
@@ -93,18 +99,50 @@ def append_resident_control_loop_receipt(
9399
repo_root=repo_root,
94100
created_at=created_at,
95101
)
96-
target = Path(path)
97-
target.parent.mkdir(parents=True, exist_ok=True)
98-
with target.open("a", encoding="utf-8", newline="\n") as handle:
99-
handle.write(json.dumps(receipt.to_dict(), sort_keys=True, separators=(",", ":")))
100-
handle.write("\n")
102+
line = json.dumps(receipt.to_dict(), sort_keys=True, separators=(",", ":")) + "\n"
103+
if len(line.encode("utf-8")) > 64 * 1024:
104+
raise ValueError("resident_control_loop_receipt_too_large")
105+
secure_append_runtime_text(
106+
path,
107+
line,
108+
repo_root=repo_root,
109+
validate_existing=_validate_existing_receipts,
110+
)
101111
return receipt
102112

103113

104-
def _string_tuple(value: Any) -> tuple[str, ...]:
114+
def _string_tuple(value: Any, *, max_chars: int) -> tuple[str, ...]:
105115
if not isinstance(value, (list, tuple)):
106116
return ()
107-
return tuple(str(item) for item in value if str(item or "").strip())
117+
return tuple(
118+
_bounded_text(item, max_chars)
119+
for item in value[:128]
120+
if str(item or "").strip()
121+
)
122+
123+
124+
def _bounded_text(value: Any, max_chars: int) -> str:
125+
return str(value or "").strip()[:max_chars]
126+
127+
128+
def _validate_existing_receipts(existing: str) -> None:
129+
for line_number, raw in enumerate(existing.splitlines(), start=1):
130+
if not raw.strip():
131+
continue
132+
if len(raw.encode("utf-8")) > 64 * 1024:
133+
raise ValueError(f"resident_control_loop_receipt_line_too_large:{line_number}")
134+
try:
135+
payload = json.loads(raw)
136+
except json.JSONDecodeError as exc:
137+
raise ValueError(
138+
f"resident_control_loop_receipt_chain_invalid_json:{line_number}"
139+
) from exc
140+
if not isinstance(payload, dict):
141+
raise ValueError(f"resident_control_loop_receipt_chain_invalid:{line_number}")
142+
if payload.get("schema_version") != CONTROL_LOOP_RECEIPT_SCHEMA_VERSION:
143+
raise ValueError(f"resident_control_loop_receipt_schema_invalid:{line_number}")
144+
if not str(payload.get("receipt_id") or "").strip():
145+
raise ValueError(f"resident_control_loop_receipt_id_missing:{line_number}")
108146

109147

110148
def _int(value: Any) -> int:

modules/communication/moltbot_bridge/src/reddog_resident_queue_binding_profile.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
from pathlib import Path
1919
from typing import Mapping
2020

21+
from modules.infrastructure.shared_utilities.runtime_artifact_safety import (
22+
validate_runtime_artifact_path,
23+
validate_runtime_root_path,
24+
)
25+
2126

2227
ENV_REDDOG_RESIDENT_QUEUE_BINDING_PROFILE = "REDDOG_RESIDENT_QUEUE_BINDING_PROFILE"
2328
PROFILE_SIGNED_0102_BOUNDED_CODE = "signed_0102_bounded_code"
@@ -187,29 +192,42 @@ def resident_queue_runtime_root_path(env: Mapping[str, str], repo_root: Path | s
187192
path = Path(raw)
188193
if not path.is_absolute():
189194
path = root.parent / path
190-
return str(path.resolve())
195+
return str(validate_runtime_root_path(path, repo_root=root))
191196
if resident_queue_binding_profile(env) not in RESIDENT_QUEUE_PROFILES:
192197
return ""
193-
return str(root.parent / ".reddog" / "resident" / _repo_slug(root))
198+
runtime_root = root.parent / ".reddog" / "resident" / _repo_slug(root)
199+
return str(validate_runtime_root_path(runtime_root, repo_root=root))
194200

195201

196202
def resident_queue_runtime_file_path(
197203
env: Mapping[str, str],
198204
repo_root: Path | str,
199205
env_name: str,
200206
) -> str:
201-
"""Return an explicit env path or a profile-derived runtime file path."""
207+
"""Return a confined explicit or profile-derived runtime file path."""
202208

203-
raw = str(env.get(env_name) or "").strip()
204-
if raw:
205-
return raw
206209
filename = PROFILE_RUNTIME_PATH_FILENAMES.get(env_name)
207210
if not filename:
208211
return ""
209-
root = resident_queue_runtime_root_path(env, repo_root)
210-
if not root:
212+
raw = str(env.get(env_name) or "").strip()
213+
runtime_root = resident_queue_runtime_root_path(env, repo_root)
214+
if raw:
215+
return str(
216+
validate_runtime_artifact_path(
217+
raw,
218+
repo_root=repo_root,
219+
allowed_root=runtime_root or None,
220+
)
221+
)
222+
if not runtime_root:
211223
return ""
212-
return str(Path(root) / filename)
224+
return str(
225+
validate_runtime_artifact_path(
226+
Path(runtime_root) / filename,
227+
repo_root=repo_root,
228+
allowed_root=runtime_root,
229+
)
230+
)
213231

214232

215233
def resident_queue_artifact_generator_mode(env: Mapping[str, str]) -> str:

modules/communication/moltbot_bridge/tests/TestModLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
**Truth boundary**: Tests use injected probes and do not perform live side
4040
effects. The production live canary remains unexecuted.
4141

42+
## 2026-07-18: Runtime Artifact Confinement
43+
44+
- Added source-path, runtime-root escape, malformed-chain, and concurrent
45+
receipt append regression tests.
46+
4247
## 2026-07-18: REDDOG_HOLOINDEX_QUERY_OWNER_BOUNDARY_POC_PHASE1
4348

4449
**Files**: test_reddog_holoindex_query_boundary.py (NEW),

0 commit comments

Comments
 (0)