T09 · Insecure Skill Coding Practices
- Location
scripts/xpulse.py:514- Finding
Position-gate configuration is ignored, causing unnecessary credential access and authenticated network requests
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill mostly does what it says, but it can still read Kalshi credentials and request private position data even when its documented position gate is disabled.
Review or patch the `position_gate` behavior before installing if you expect to run without Kalshi position matching. Treat the local cache, history, debug logs, and Kalshi key path as sensitive trading data, use pinned dependencies from `requirements.txt`, and avoid the optional bulk stack install unless you have reviewed those other skills too.
scripts/xpulse.py:514Position-gate configuration is ignored, causing unnecessary credential access and authenticated network requests
SKILL.md:33Unpinned installation guidance and optional bulk Skill installation increase supply-chain exposure
The declared description promises a substantial social-signal scanning and filtering system for prediction market trading. However, the provided code chunk contains only an init.py file with a package docstring and no operational logic. Since the actual code does not implement or demonstrate any of the declared capabilities, the description does not accurately represent this supplied code chunk.
The declared description describes an end-user trading signal scanner with external content search, AI-based filtering, and position-aware alerting. The supplied code chunk is instead a low-level helper module for robustly parsing JSON-like responses from model output. It only manipulates input strings locally using json, regex, and brace matching. While such a utility could support a larger pipeline, this specific chunk does not implement the declared scanner behavior or any of its key capabilities. Therefore the description does not accurately represent what this code chunk actually does.
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
Manual reset:
rm ~/.openclaw/state/x_signal_history.json
The skill documentation describes capabilities that require network access, local model invocation, filesystem writes, environment/config access, and scheduled execution, but it does not declare any explicit tool scope or permission boundaries. In an agent ecosystem, missing scope declarations weaken least-privilege controls and can cause the skill to receive broader access than users expect.
The skill processes sensitive trading topics and active Kalshi position data and sends derived content to external or local services such as DuckDuckGo, Kalshi, and Ollama, yet the description lacks a clear privacy warning or data-flow disclosure. Users may unknowingly expose trading interests, positions, and market thesis information to services or logs, which can create confidentiality and operational-risk issues.
The cache description states that all detected signals are written to .x_signal_cache.json, including suppressed signals, without warning that this creates a local retention store of potentially sensitive trading intelligence. If the host is shared, backed up, synced, or compromised, this cache could reveal market focus, inferred positions, and timing-sensitive information.
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.
Via crontab:
# Add to crontab -e:
*/30 * * * * cd /path/to/xpulse && python -m xpulse.xpulse >> /tmp/xpulse.log 2>&1
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.
Via launchd (macOS):
<!-- ~/.launchd/com.xpulse.scanner.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
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.
Via launchd (macOS):
<!-- ~/.launchd/com.xpulse.scanner.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
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.
Via launchd (macOS):
<!-- ~/.launchd/com.xpulse.scanner.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
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.
Via launchd (macOS):
<!-- ~/.launchd/com.xpulse.scanner.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
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.
<!-- ~/.launchd/com.xpulse.scanner.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
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.
**Expected behavior:** If you have no active Kalshi positions, all signals are suppressed (logged silently). This is intentional — position-aware alerting is the goal.
**To test:** Either:
1. Create a test Kalshi position matching your topic
2. Disable Stage 3: set `position_gate: false` in config (alerts all non-materiality signals)
### Signal History Growing Too Large
The skill is designed to generate direct alerts and integrate with trade-oriented tooling, but it does not present a clear caution that automated or semi-automated signals can influence trading decisions and cause financial harm if incorrect, manipulated, or delayed. In a trading context, omission of that warning increases the risk of overreliance on noisy or adversarial social data.
The documentation labels unmatched signals as 'silent' and the system as 'fail-closed', but it also states those signals are retained in state and cache for later consumption. That creates a confidentiality and expectation gap: users may believe unmatched portfolio-relevant signal data is discarded when it is actually persisted and accessible through other components.
This is a real issue because the 'suppressed' terminology suggests non-delivery and non-exposure, while the design actually exposes the data programmatically and via cache. In a trading context, suppressed signals may still reveal market interests, position alignment, or sensitive summaries to other local tools or users with access to state artifacts.
The file documents storage of signal topics, summaries, confidence, and matched position metadata in state/cache and debug logging without a clear user-facing disclosure of retention. In a prediction-market workflow, that local telemetry can reveal trading interests and potentially sensitive portfolio context, making it a legitimate privacy and data-handling weakness.
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.
model = OLLAMA_CFG.get("model", "qwen3:latest")
try:
import urllib.request
req = urllib.request.Request("http://localhost:11434/api/tags")
with urllib.request.urlopen(req, timeout=5) as resp:
data = json.loads(resp.read().decode("utf-8"))
models = [m.get("name", "") for m in data.get("models", [])]
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.
"options": {"num_predict": 256}, # Cap output tokens — we only need short JSON
}).encode("utf-8")
req = urllib.request.Request(
"http://localhost:11434/api/generate",
data=payload,
headers={"Content-Type": "application/json"},
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# Fallback: CLI with stdin pipe
try:
result = subprocess.run(
["ollama", "run", model],
input=prompt,
capture_output=True, timeout=timeout, text=True
The skill reads Kalshi API credentials and a local private key even though its headline purpose is X/Twitter signal scanning. That expands the trust boundary significantly: compromise of this skill, prompt-driven misuse of downstream components, or log/debug leakage could expose trading credentials and enable unauthorized access to the user's portfolio context.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
private_key = f.read()
config = Configuration(
host="https://api.elections.kalshi.com/trade-api/v2"
)
config.api_key_id = api_key_id
config.private_key_pem = private_key
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
private_key = f.read()
config = Configuration(
host="https://api.elections.kalshi.com/trade-api/v2"
)
config.api_key_id = api_key_id
config.private_key_pem = private_key
The inline comment claims 'Cache all Stage 1 signals for morning brief,' but the signals list at this point only contains topics whose analysis returned has_signal and met min_confidence. This contradicts the documented intent and could mislead maintainers about what downstream components receive.
No suspicious patterns detected.