Back to skill

Security audit

Rootly Morning Brief

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Rootly briefing purpose, but its script can forward bearer credentials to user-configurable or API-provided arbitrary URLs, so it needs careful review before installation.

Install only if you are comfortable giving the skill a Rootly API key and having generated incident/on-call digests posted by OpenClaw cron. Use a read-only Rootly token, avoid private incidents unless needed, pin Slack delivery to the intended private channel, and do not set ROOTLY_BASE_URL, --base-url, or --api-key-env to untrusted values until the URL and credential-source scoping is hardened.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/rootly_morning_brief.py:342
Finding

Bearer credential disclosure to unrestricted network origins

Content
View full analysis
str: value = os.getenv(name, "").strip() if value: return value file_var_name = f"{name}_FILE" file_var_value = os.getenv(file_var_name, "").strip() if file_var_value: file_path = Path(file_var_value).expanduser() try: file_value = file_path.read_text(encoding="utf-8").strip() except OSError as exc: raise ConfigError( f"{name} is missing and {file_var_name} could not be read: {file_path} ({exc})" ) from exc if file_value: return file_value raise ConfigError( f"{name} is missing and {file_var_name} points to an empty file: {file_path}" ) ``` That value is unconditionally converted into a Bearer credential: ```python def _build_headers(api_key: str) -> dict[str, str]: return { "Authorization": f"Bearer {api_key}", "Accept": "application/vnd.api+json", "Content-Type": "application/vnd.api+json", "User-Agent": "rootly-morning-brief/1.0", } ``` Absolute HTTP and HTTPS destinations are accepted without scheme, hostname, or origin ...[truncated 6540 chars]
Remediation
View remediation
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The core data-processing purpose matches well: this script builds a Rootly morning briefing with incident, on-call, and overdue action item summaries. However, the declared description says 'generate and deliver' and says it should be used when the user wants to 'schedule a cron-based morning ops update to Slack.' The supplied code does not post to Slack, call any Slack API/webhook, or configure scheduling/cron. It only retrieves Rootly data, formats a digest, and prints it. Therefore the description overstates delivery and scheduling capabilities in a materially important way.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 43)May include surrounding context.

bash
mkdir -p "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/secrets"
printf '%s\n' 'YOUR_ROOTLY_API_KEY' > "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/secrets/rootly_api_key"
chmod 600 "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/secrets/rootly_api_key"

OpenClaw cron jobs do not inherit your shell env, so the secret file is the reliable path.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README repeatedly instructs users to deliver the generated incident digest to Slack via cron, but it never warns that the content may include sensitive operational details such as active incidents, on-call identities, overdue security-related actions, and internal links. In this context, omission matters because a misrouted or overly broad Slack destination could expose incident-response information to unintended recipients.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill declares access to sensitive capabilities in practice (environment secrets, file reads, and network use) but does not explicitly scope or constrain them with a permissions/allowed-tools policy. This increases the attack surface because an agent or future code changes may access broader resources than intended, especially given the presence of API keys and file-based secret fallback paths.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · mock-data/action_items.json (reported line 2939)May include surrounding context.

json
}
  ],
  "links": {
    "self": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "first": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev": null,
    "next": null,

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · mock-data/action_items.json (reported line 2940)May include surrounding context.

json
}
  ],
  "links": {
    "self": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "first": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev": null,
    "next": null,

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · mock-data/action_items.json (reported line 2943)May include surrounding context.

json
}
  ],
  "links": {
    "self": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "first": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev": null,
    "next": null,

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · mock-data/incidents.json (reported line 3213)May include surrounding context.

json
}
  ],
  "links": {
    "self": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "first": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev": null,
    "next": null,

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · mock-data/incidents.json (reported line 3214)May include surrounding context.

json
}
  ],
  "links": {
    "self": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "first": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev": null,
    "next": null,

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · mock-data/incidents.json (reported line 3217)May include surrounding context.

json
}
  ],
  "links": {
    "self": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "first": "https://api.rootly.com/v1/action_items?page%5Bnumber%5D=1&page%5Bsize%5D=100",
    "prev": null,
    "next": null,

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The mock incident payload embeds detailed role, permission, and administrative capability data that is not needed to generate a morning digest. Even as mock data, this overexposes privilege structure and can aid attackers or prompt downstream agents to overreach based on misleadingly broad permissions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

This Python code makes authenticated HTTP requests to the Rootly API and then formats incident, on-call, and overdue action data for stdout, which may expose operationally sensitive information. While the script's purpose implies fetching Rootly data, there is no explicit user-facing disclosure in the code that it will retrieve and output potentially sensitive incident and personnel details.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

This is a manifest-type JSON file, so vague-trigger checks apply. Phrases such as 'Run tabletop + automation simulation for break-glass production access controls' read like broad actionable instructions, but the file provides no surrounding activation rules, exclusions, or negative examples to clarify whether these are merely stored records or invocation-driving prompts.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

In a JSON/manifest-style file, imperative text like 'Compromised token blast radius review + revocation verification checklist' can be interpreted as an activation target if consumed by a skill, but there is no explicit statement of scope or context. The absence of constraints makes it ambiguous whether similar everyday security-review phrasing should invoke behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file includes user PII such as full name, email address, timezone, and profile details that are unnecessary for a generic ops briefing fixture. Unneeded personal data increases privacy exposure and can leak sensitive contact information through logs, prompts, summaries, or test artifacts.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The file hard-codes America/Toronto as the default timezone, which imposes a locale-specific presentation choice for all users unless they override it. This is a natural-language/policy concern because the default output behavior is tied to a specific region without an explicit opt-in or documented regional scope.

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/rootly_morning_brief.py (reported line 852)May include surrounding context.

python
args = parse_args()
    log_level = os.getenv("ROOTLY_BRIEF_LOG_LEVEL", "WARNING").upper()
    logging.basicConfig(
        level=getattr(logging, log_level, logging.WARNING),
        format="%(asctime)s %(levelname)s %(name)s %(message)s",
    )

Static analysis

No suspicious patterns detected.