Back to skill

Security audit

FIS Architecture

Security checks for vulnerabilities and agentic risk

Overview

This looks like a legitimate multi-agent coordination skill, but it needs review because it persists ticket state, posts task content to Discord, and has unsafe ticket-file handling.

Install only if you are comfortable with task details being shared through Discord and with the skill keeping local ticket files across sessions. Restrict Discord bot permissions and channel access, avoid posting sensitive content to public threads or #daily-chat, keep FIS_HUB_PATH confined to a dedicated directory, and review generated sessions_* commands before executing them.

Vulnerability Patterns
  • 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
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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 (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/fis_lifecycle_pro.py:279
Finding

Path Traversal in Ticket File Operations

Content
View full analysis

Vulnerability Details

File Location: scripts/fis_lifecycle_pro.py:279-306
Vulnerability Type: Unsanitized path construction using attacker-controlled ticket identifiers
Risk Level: High

Vulnerable Code

python
def archive_ticket(self, ticket_id):
    """Archive a completed ticket"""
    # Check completed first
    src = os.path.join(self.completed_dir, f"{ticket_id}.json")

    if not os.path.exists(src):
        # Check active
        src = os.path.join(self.active_dir, f"{ticket_id}.json")
        if not os.path.exists(src):
            print(f"✗ Ticket not found: {ticket_id}")
            return False

    with open(src, "r") as f:
        ticket = json.load(f)

    ticket["status"] = "archived"
    ticket["archived_at"] = datetime.now().isoformat()
    ticket["updated_at"] = datetime.now().isoformat()

    with open(src, "w") as f:
        json.dump(ticket, f, indent=2)

    # Move to archive
    dst = os.path.join(self.archive_dir, f"{ticket_id}.json")
    os.rename(src, dst)

    print(f"⚪ Archived: {ticket_id}")
    return True

The same unsafe path-construction pattern also appears in the following operations:

  • update_status: scripts/fis_lifecycle_pro.py:163
  • complete_ticket: scripts/fis_lifecycle_pro.py:202,226
  • archive_ticket: scripts/fis_lifecycle_pro.py:282-302
  • get_ticket: scripts/fis_lifecycle_pro.py:349

Technical Analysis

The ticket_id value originates from CLI arguments and is inserted directly into filesystem paths:

python
os.path.join(self.active_dir, f"{ticket_id}.json")

No validation prevents the identifier from containing absolute paths, .. traversal components, path separators, or symlink-based escapes. os.path.join does not enforce containment within the intended ticket directory. As a result, a value such as ../../target can resolve to target.json outside the active, completed, or archived ticket directory.

The affected methods perform sensitive operations ...[truncated 2189 chars]

Remediation
View remediation

Remediation Suggestions

  1. Enforce a strict ticket identifier format before any filesystem operation. For example:
python
import re

TICKET_ID_PATTERN = re.compile(r"^TASK_[A-Za-z0-9_]+$")

def validate_ticket_id(ticket_id: str) -> str:
    if not TICKET_ID_PATTERN.fullmatch(ticket_id):
        raise ValueError("Invalid ticket identifier")
    return ticket_id
  1. Centralize path construction and verify containment after canonicalization:
python
from pathlib import Path

def safe_ticket_path(directory: str, ticket_id: str) -> Path:
    validate_ticket_id(ticket_id)

    base = Path(directory).resolve()
    candidate = (base / f"{ticket_id}.json").resolve()

    if candidate.parent != base:
        raise ValueError("Ticket path escapes the ticket directory")

    return candidate
  1. Use the centralized helper in update_status, complete_ticket, archive_ticket, and get_ticket rather than constructing paths independently.

  2. Reject identifiers containing /, \, .., null bytes, or absolute-path syntax, even if additional validation is introduced elsewhere.

  3. Account for symlink attacks. Where practical, ensure ticket directories and files are not symlinks and use secure descriptor-based file operations with no-follow behavior.

  4. Run the Skill under a dedicated, minimally privileged account that cannot modify unrelated OpenClaw configuration or user files.

  5. Add negative tests covering traversal identifiers, absolute paths, mixed separators, symlink escapes, and malformed ticket IDs.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fis_worker_toolkit.py:20
Finding

Unescaped User Input in Generated Agent Tool Calls

Content
View full analysis

Vulnerability Details

File Location: scripts/fis_worker_toolkit.py:20-49
Vulnerability Type: Injection into executable-looking Agent tool-call templates
Risk Level: Medium

Vulnerable Code

python
def spawn_for_subtask(self, parent_ticket_id, subtask_description):
    """Spawn sub-agent for complex subtask - runs in background"""
    print("=" * 60)
    print("🔄 Spawning SubAgent for Complex Subtask")
    print("=" * 60)

    sub_agent = self._select_sub_agent(subtask_description)

    sub_ticket_id, sub_ticket = self.fis.create_ticket(
        agent=sub_agent,
        task=f"[Subtask of {parent_ticket_id}] {subtask_description}",
        role="subagent",
    )

    print(f"\n✅ Sub-ticket created: {sub_ticket_id}")
    print(f"SubAgent: {sub_agent}")
    print(f"Parent: {parent_ticket_id}")

    spawn_cmd = f"""sessions_spawn(
    agentId="{sub_agent}",
    task="{subtask_description}",
    mode="run",
    label="{sub_ticket_id}"
)"""

    print(f"\n📋 Execute this to spawn sub-agent:")
    print(spawn_cmd)

A related unsafe template is generated in scripts/fis_worker_toolkit.py:94-96:

python
print(
    f'sessions_send(\n    sessionKey="main",\n    message="{report[:100]}..."\n)'
)

Task text is also embedded into message and command-oriented templates in scripts/fis_coordinator.py:75-105 and scripts/fis_coordinator.py:155-177.

Technical Analysis

The toolkit interpolates attacker-influenced subtask, summary, and report content directly into strings that resemble executable Agent tool calls. The inserted values are placed inside quoted arguments without JSON encoding, escaping, or structural validation.

For example, a subtask_description containing quotation marks, newlines, closing syntax, or additional instructions can terminate the intended task value and alter the generated template. The application itself only prints the resulting string; it does not directly execute sessions_spawn or `sessions_ ...[truncated 2129 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not generate executable tool calls through string interpolation. Invoke typed Agent APIs directly with separately supplied arguments whenever the runtime permits it.

  2. If textual serialization is unavoidable, encode every value using a suitable serializer rather than manually adding quotation marks:

python
import json

spawn_cmd = (
    "sessions_spawn(\n"
    f"    agentId={json.dumps(sub_agent)},\n"
    f"    task={json.dumps(subtask_description)},\n"
    '    mode="run",\n'
    f"    label={json.dumps(sub_ticket_id)}\n"
    ")"
)
  1. Prefer a strict JSON data envelope that cannot be confused with instructions:
python
payload = {
    "agentId": sub_agent,
    "task": subtask_description,
    "mode": "run",
    "label": sub_ticket_id,
}
print(json.dumps(payload, ensure_ascii=False))
  1. Clearly label task descriptions, summaries, and reports as untrusted data. Receiving Agents should be instructed not to interpret content inside those fields as tool commands or higher-priority instructions.

  2. Require explicit operator confirmation showing the parsed destination Agent, mode, label, and complete task before dispatch.

  3. Validate permitted Agent IDs and enforce length limits for task, summary, deliverable, and report fields.

  4. Apply the same structured serialization to sessions_send templates and to the related coordinator templates.

  5. Add tests using quotation marks, backslashes, multiline input, closing parentheses, code fences, and embedded tool-call text to verify that user data cannot alter the generated call structure.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (9)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill documents shell execution, filesystem access, and environment-dependent operations, but it does not declare any tool scope such as permissions or allowed-tools. That creates a trust gap where an operator or platform may expose broader capabilities than intended, increasing the chance of unsafe command execution or file modification during use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This workflow instructs operators to send task content and status updates to Discord threads, which is an external service, without any warning about data sensitivity, retention, or access scope. Sensitive prompts, internal project details, or user data could be disclosed to channel members or stored externally without informed consent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The instruction to archive the thread and report to a public chat channel encourages redistribution of task results to a broader audience without any disclosure warning or access control guidance. If outputs contain sensitive code, research, operational details, or user information, this step can amplify exposure beyond the original need-to-know group.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 256)May include surrounding context.

md
- Check disk space

**If Thread creation fails:**
- Verify the bot has **Create Public Threads** permission in the target Forum channel
- Check that `channelId` points to a Forum channel (not a regular text channel)
- Confirm the bot is a member of the server with correct roles

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The generated worker instructions explicitly require the recipient to confirm receipt using the Chinese phrase "收到任务,开始执行." This imposes a specific language choice in natural-language output and does not offer any user or agent opt-in or alternative locale handling.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The manifest describes orchestrating multi-agent workflows using JSON tickets and A2A coordination between agents. This code additionally executes the local openclaw binary via subprocess.run to discover session context, which is a broader host-execution capability than the stated purpose requires and is not declared in the manifest description.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/fis_lifecycle_pro.py (reported line 118)May include surrounding context.

python
# This subprocess call is intentional — it's the only way to detect
        # the current session context when running outside the OpenClaw runtime.
        try:
            result = subprocess.run(
                ["openclaw", "sessions", "list", "--json"],
                capture_output=True,
                text=True,

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The module and method text explicitly state that sub-agents are spawned and run in the background, but the implementation only prints a sessions_spawn(...) string and never performs the launch. In an orchestration skill, this mismatch can cause operators or upstream agents to assume delegated work is executing when it is not, leading to silent task loss, broken monitoring, and incorrect trust in completion or progress state.

Content

No source excerpt is available for this finding.

Excessive Permissions

Low
Category
Privilege Escalation
Confidence
80% confidence
Finding

Skill requests more permissions than appear necessary for its stated functionality. Review if elevated access is justified.

Content

Scanner excerpt · SKILL.md (reported line 39)May include surrounding context.

md
Each agent's Discord bot **must** have these permissions configured in the Discord server. Without them, Thread creation and messaging will fail silently.

**Required Bot Permissions:**
- **Send Messages** — reply in channels and threads
- **Send Messages in Threads** — post inside Forum threads
- **Create Public Threads** — create new Forum posts programmatically

Static analysis

No suspicious patterns detected.