T09 · Insecure Skill Coding Practices
- Location
scripts/pb_module_triage.py:247- Finding
Arbitrary Code Execution Through Untrusted Protobuf Module Import
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill has a coherent PB-log triage purpose, but it needs review because its helper script can execute Python code from the target project’s protobuf directory and can read very large recordings into memory.
Install only if you will use it on trusted BrainNode repositories and trusted generated protobuf bindings. Do not point --repo-root or --proto-dir at untrusted directories, prefer a sandboxed/runtime-limited environment, and lower --max-file-bytes for large recordings. Expect it to read PB files from the project and optionally write a JSON report when requested.
scripts/pb_module_triage.py:247Arbitrary Code Execution Through Untrusted Protobuf Module Import
scripts/pb_module_triage.py:990Denial of Service Through Whole-File Protobuf Loading
The skill instructs the agent to read project files, locate PB recordings, and optionally write JSON reports, but it does not declare any explicit tool scope such as allowed tools or permissions. In practice this can let a host agent grant broader-than-necessary file access, increasing the risk of unintended reading of unrelated files or writing outputs in unsafe locations when handling adversarial paths or large repositories.
This instruction imposes a default language choice in the skill's output contract rather than preserving user locale unless they opt in. That can violate language/locale policy because it forces a specific language by default for all users, regardless of their preferred language.
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
if stamp > 0:
return stamp
for name in ("point_cloud", "image", "wifi_data"):
child = getattr(message, name, None)
if child is not None:
stamp = stamp_ns_from_header(getattr(child, "header", None))
if stamp > 0:
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
if stamp > 0:
return stamp
for name in ("gps", "imu", "h264", "rangeframe"):
items = getattr(message, name, [])
if items:
stamp = stamp_ns_from_header(getattr(items[0], "header", None))
if stamp > 0:
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
count = 0
for field_name, _, _, _ in BUFFER_SPECS:
if hasattr(total, field_name):
count += len(getattr(total, field_name))
return count
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
for type_name, module, label in direct_message_candidates(pb2):
if not hasattr(pb2, type_name):
continue
message = getattr(pb2, type_name)()
try:
message.ParseFromString(raw)
except DecodeError:
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
for field_name, type_name, module, _ in BUFFER_SPECS:
if not hasattr(total, field_name) or not hasattr(pb2, type_name):
continue
message_cls = getattr(pb2, type_name)
for payload in getattr(total, field_name):
message = message_cls()
try:
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
if not hasattr(total, field_name) or not hasattr(pb2, type_name):
continue
message_cls = getattr(pb2, type_name)
for payload in getattr(total, field_name):
message = message_cls()
try:
message.ParseFromString(payload)
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
if getattr(frame, "task_id", ""):
task_ids[getattr(frame, "task_id")] += 1
for name, counter in modes.items():
counter[enum_name(frame, name, int(getattr(frame, name, 0)))] += 1
for name, counter in states.items():
counter[enum_name(frame, name, int(getattr(frame, name, 0)))] += 1
summary.metrics["modes"] = {name: counter_to_dict(counter) for name, counter in modes.items()}
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
if getattr(frame, "task_id", ""):
task_ids[getattr(frame, "task_id")] += 1
for name, counter in modes.items():
counter[enum_name(frame, name, int(getattr(frame, name, 0)))] += 1
for name, counter in states.items():
counter[enum_name(frame, name, int(getattr(frame, name, 0)))] += 1
summary.metrics["modes"] = {name: counter_to_dict(counter) for name, counter in modes.items()}
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
for field_name in status_fields:
if not has_field(frame, field_name):
continue
status = getattr(frame, field_name)
level = int(getattr(status, "level", 0))
name = getattr(status, "name", "") or field_name
names[name] += 1
This code file performs a file write when --output is provided, but there is no confirmation prompt, visible user-facing log message, or explanatory comment/docstring near the write. For a safety review under this rule, file writes should include some form of disclosure unless clearly warned elsewhere.