Back to skill

Security audit

Webhook Promo Scheduler

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Discord posting purpose, but its webhook credential handling and destination controls need review before use.

Install only if you are comfortable reviewing and controlling how it is invoked. Do not pass real Discord webhook URLs directly on shared shells, CI logs, or scheduler command lines; use a protected secret mechanism or wrapper. Restrict the webhook destination to Discord before exposing this through automation, and do not rely on the ledger as a hard anti-spam guarantee under concurrent jobs.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/promo_scheduler.py:137
Finding

Webhook Credential Exposure Through Command-Line Arguments

Content
View full analysis
\ --channel openclaw-discord \ --message "Hello from OpenClaw!" ``` ```bash python3 {baseDir}/scripts/promo_scheduler.py rotate \ --webhook-url \ --channel openclaw-discord \ --messages-file messages.txt ``` ### Technical Analysis A Discord webhook URL contains authentication material and should be treated as a credential. Although the application redacts the value from its own status messages, accepting it as a command-line argument exposes it outside the application's logging controls. Depending on the operating environment, command-line arguments may be retained or exposed through: - Interactive shell history. - Process listings and process-inspection interfaces. - CI/CD job metadata and command logs. - Scheduler definitions and service configuration. - Endpoint monitoring and process telemetry. - Debugging or diagnostic data. Consequently, the claim that the URL is “never printed” does not provide end-to-end protection for the credential. Application-level output redaction cannot prevent exposure by the shell or operating system. ### Attack Path 1. A user follows the documented example and supplies the Discord webhook URL through `--webhook-url`. 2. The shell records the command in its history, or the operating system exposes the argument through process inspection. 3. A local user, monitoring service, CI operator, or attacker with access to those records obtains the webhook URL. 4. The attacker sends reque ...[truncated 664 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/post_webhook.py:36
Finding

Unrestricted Webhook Destination Enables Arbitrary Outbound Requests

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/promo_scheduler.py:54
Finding

Non-Atomic Ledger Enforcement Permits Duplicate Daily Posts

Content
View full analysis
int: ledger = Ledger(args.ledger_path) today = today_local_yyyy_mm_dd() if ledger.already_posted_today(args.channel, today=today): eprint(f"Refusing to post: already posted today for channel={args.channel} date={today}") return 3 message = args.message msg_hash = sha256_hex(message) ledger.append(LedgerEntry(date=today, channel=args.channel, status="reserved", hash=msg_hash)) ``` The check only recognizes entries with a `posted` status: ```python def already_posted_today(self, channel: str, today: Optional[str] = None) -> bool: today_s = today or today_local_yyyy_mm_dd() with self._open_locked("a+") as f: f.seek(0) for line in f: line = line.strip() if not line: continue try: obj = json.loads(line) except Exception: continue if not isinstance(obj, dict): continue if obj.get("date") != today_s: continue if obj.get("channel") != channel: continue if obj.get("status") == "posted": return True return False ``` Lock acquisition failures are also silently ignored: ```python if fcntl is not None: try: fcntl.flock(f.fileno(), fcntl.LOCK_EX) except Exception: pass ``` ### Technical Analysis The intended invariant is a maximum of one successful post per day for each logical channel. File locking is applied separately inside `already_posted_today()` and `append()`, but the lock is released between the check and the reservation. ...[truncated 1812 chars]
Remediation
View remediation
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill description claims scheduling and anti-spam ledger protections, but the analysis indicates those controls may be absent from the actual implementation. When security-relevant safeguards are documented but not enforced, users may trust the skill to prevent repeated posting or provide auditability when it does not, increasing the risk of webhook abuse, spam, or unintended message delivery.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill advertises network and file-based behavior but does not declare any explicit tool scope or permissions boundaries. In an agent environment, this can lead to over-broad execution authority, making it easier for the skill to read local files or make outbound requests without clear user review or policy enforcement.

Content

No source excerpt is available for this finding.