Back to skill

Security audit

Agent Os

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a local multi-agent file workflow, but it under-discloses external memory reads and has persistent shared-context risks that users should review before installing.

Install only if you are comfortable with a skill that creates persistent shared agent state and review the scripts before use. Prefer project-level installation, use --dry-run first, avoid running mission_control.py where .workbuddy/memory contains private logs, and treat generated context packs or mission-control files as untrusted data when collaborators can create findings.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/mission_control.py:147
Finding

Undisclosed Access to WorkBuddy Memory Outside the Declared Project Boundary

Content
View full analysis
Path | None: """Find the .workbuddy/memory directory for daily logs.""" candidates = [] if project_root: candidates.append(Path(project_root) / ".workbuddy" / "memory") candidates.extend([ Path.cwd() / ".workbuddy" / "memory", Path.home() / "Agent" / "WorkBuddy" / "Claw" / ".workbuddy" / "memory", ]) for c in candidates: if c.is_dir(): return c return None ``` ```python def _compute_leverage_ratio( memory_dir: Path | None = None, lookback_days: int = LEVERAGE_LOOKBACK_DAYS, ) -> dict[str, Any]: result = { "strategic": 0, "tactical": 0, "maintenance": 0, "total": 0, "ratio": 0.0, "status": "no_data", "emoji": "⚪", } if memory_dir is None: memory_dir = _find_memory_dir() if memory_dir is None or not memory_dir.is_dir(): return result today = datetime.now(timezone.utc).replace(tzinfo=None) for offset in range(lookback_days): day = today - timedelta(days=offset) log_file = memory_dir / f"{day.strftime('%Y-%m-%d')}.md" if not log_file.exists(): continue try: content = log_file.read_text(encoding="utf-8") except Exception: continue for line in content.splitlines(): match = LEVERAGE_PATTERN.search(line) if match: emoji = match.group() if emoji == LEVERAGE_STRATEGIC: result["strategic"] += 1 elif emoji == LEVERAGE_TACTICAL: result["tactical"] += 1 elif emoji == LEVERAGE_MAINTENANCE: resu ...[truncated 2272 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
scripts/upsert_finding.py:125
Finding

Persistent Prompt Injection Through Unsanitized Finding Fields

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mission_control.py:380
Finding

Path Traversal and Arbitrary Markdown File Overwrite Through the Date Argument

Content
View full analysis
int: parser = build_parser() args = parser.parse_args(argv) agent_os_root = _find_agent_os_root(args.project_root) date = args.date or _today() filename = f"{date}.md" content = generate_mission_control(agent_os_root, date) if args.dry_run: print(f"[DRY RUN] Would write to: {agent_os_root / MISSION_CONTROL_DIR / filename}") print("---") print(content) return 0 mc_dir = agent_os_root / MISSION_CONTROL_DIR mc_dir.mkdir(parents=True, exist_ok=True) out_path = mc_dir / filename out_path.write_text(content, encoding="utf-8") print(f"Mission control written to: {out_path}") return 0 ``` The argument is described as a date but has no format validation: ```python parser.add_argument("--date", default=None, help="Date for the report (YYYY-MM-DD, default: today)") ``` ### Technical Analysis The value supplied through `--date` is used directly as part of an output path. The implementation does not parse it as a date, reject path separators, canonicalize the result, or verify that the resolved destination remains under `.agent-os/mission-control`. A relative value containing `../` components can escape the intended output directory. An absolute path is also dangerous because `pathlib` discards the preceding path when the appended component is absolute. The `.md` suffix limits direct targeting to Markdown-named files, but the script can still overwrite any writable destination ending in the generated suffix or any sensitive project documentation selected through traversal. ### Attack Path 1. An attacker controls or influences the command-line arguments used to invoke `mission_control.py`. 2. The attacker ...[truncated 906 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/upsert_finding.py:39
Finding

Cross-Project State Access Through Unrestricted Parent-Directory Discovery

Content
View full analysis
Path: """Locate the .agent-os directory, walking up from project_root or cwd.""" start = Path(project_root) if project_root else Path.cwd() candidate = start / DEFAULT_AGENT_OS_DIR if candidate.is_dir(): return candidate # Walk up one level for parent in start.parents: candidate = parent / DEFAULT_AGENT_OS_DIR if candidate.is_dir(): return candidate # Not found — return default (will be created on write) return start / DEFAULT_AGENT_OS_DIR ``` The context compiler uses the same unrestricted parent traversal: ```python def _find_agent_os_root(project_root: str | None = None) -> Path: start = Path(project_root) if project_root else Path.cwd() candidate = start / DEFAULT_AGENT_OS_DIR if candidate.is_dir(): return candidate for parent in start.parents: candidate = parent / DEFAULT_AGENT_OS_DIR if candidate.is_dir(): return candidate return start / DEFAULT_AGENT_OS_DIR ``` Mission Control also uses equivalent discovery logic: ```python def _find_agent_os_root(project_root: str | None = None) -> Path: start = Path(project_root) if project_root else Path.cwd() candidate = start / DEFAULT_AGENT_OS_DIR if candidate.is_dir(): return candidate for parent in start.parents: candidate = parent / DEFAULT_AGENT_OS_DIR if candidate.is_dir(): return candidate return start / DEFAULT_AGENT_OS_DIR ``` ### Technical Analysis All three scripts search every ancestor of the starting directory for an existing `.agent-os` directory. This also occu ...[truncated 1655 chars]
Remediation
View remediation
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (10)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The trigger list includes several broad phrases such as "audit finding," "mission control," and "agent handoff" that can plausibly appear in many unrelated tasks. In a skill with powerful allowed tools including Read, Write, Edit, Bash, Glob, and Grep, overly broad activation increases the chance the skill engages unexpectedly and causes unintended file access or workspace modifications.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The scenario trigger "working with multiple agents on the same workspace and need shared state, audit trails, or human decision compression" is broad and subjective, with no clear exclusion criteria. Because the skill prescribes reading and writing protocol files and has access to shell and file manipulation tools, ambiguous activation can cause it to run in contexts where the user did not intend this collaboration layer to modify project state.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This markdown file includes fixed Chinese-language output strings for warning, critical, and action text in the leverage audit workflow. The file does not indicate that users can choose the language or that the workflow is limited to a Chinese-language workspace, so this is a natural-language locale policy violation.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script documentation says it reads from .agent-os inputs, but the implementation also searches .workbuddy/memory, including a hard-coded path under the user's home directory. That undocumented cross-directory data access can surprise users, pull in unrelated personal/project data, and leak its presence into generated reports, which is a legitimate security/privacy concern for an agent skill.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This Python file emits several user-facing report strings in Chinese while the rest of the tool and CLI are in English. That creates a language/locale policy concern because users are not given an opt-in or configuration choice for the mixed-language output, and no region-specific justification is documented.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

This markdown file states that Chinese trigger words were added, but it does not indicate whether the skill allows users to choose language/locale or whether this locale behavior is narrowly justified. Under the policy rule for natural-language violations, introducing a specific language trigger set without documenting user choice can be a locale-policy concern.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
62% confidence
Finding

The file includes bilingual English and Chinese text throughout, but it does not explain whether users can choose their preferred language or whether the bilingual format is intentional policy. This may create a minor language/locale policy concern because the documentation format is imposed without explicit opt-in or justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

This markdown file explains that Agent OS creates and updates project files such as findings, context packs, mission control views, and flight records. While the behavior is documented functionally, there is no explicit user warning that invoking the scripts will write to or modify repository files, which is relevant to user data and workspace integrity.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code performs a persistent file write to .agent-os/findings.jsonl, including creating the directory if needed, but does not prompt for confirmation before modifying data. While the module docstring states that it creates or updates findings, there is no explicit warning at the write site that running create/update will overwrite the findings file contents.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/upsert_finding.py (reported line 188)May include surrounding context.

python
finding["status"] = args.status

    for field in ("title", "owner", "next_action", "verification", "repo", "source"):
        val = getattr(args, field, None)
        if val:
            finding[field] = val