Back to skill

Security audit

IBKR Autonomous Trader

Security checks for vulnerabilities and agentic risk

Overview

This trading skill is not clearly malicious, but it needs Review because it can place brokerage orders and automatically close every visible position on shutdown without a separate confirmation.

Review carefully before installing. Run only against an isolated IBKR paper account unless the shutdown liquidation, final risk checks, live-mode gating, dependency pinning, and auto-tuning approval controls are fixed. Do not connect this skill to a live or mixed-use brokerage account as written.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/trader.py:247
Finding

Routine Shutdown Liquidates All Positions in the Connected Account

Content
View full analysis

Vulnerability Details

File Location: scripts/trader.py:247-251; scripts/execution_engine.py:183-190
Vulnerability Type: Missing ownership and authorization boundary for destructive account operations
Risk Level: High

Vulnerable Code

python
except Exception as e:
    logger.exception(f"Unhandled exception in main loop: {e}")
finally:
    execution_engine.close_all_positions()
    ib.disconnect()
    logger.info("Disconnected from IBKR.")
python
def close_all_positions(self) -> None:
    """Emergency close all open positions (shutdown hook)."""
    positions = self.get_positions()
    logger.warning(f"Closing all {len(positions)} positions...")
    for pos in positions:
        self.close_trade(pos)

The invoked closing operation submits an opposing market order for each returned account position:

python
def close_trade(self, position: Position) -> bool:
    """Market-close an open position."""
    symbol = position.contract.symbol
    qty = abs(int(position.position))
    action = "SELL" if position.position > 0 else "BUY"
    contract = position.contract
    try:
        order = MarketOrder(action, qty)
        trade = self.ib.placeOrder(contract, order)
        self.ib.sleep(1)
        logger.info(f"Closing position: {action} {qty}x {symbol}")
        return True
    except Exception as e:
        logger.error(f"close_trade({symbol}) failed: {e}")
        return False

Technical Analysis

The shutdown hook obtains every position visible through ib.positions() and submits a market order to close each one. It does not distinguish between positions created by this Skill and positions created manually, by another IBKR client, or by another automated strategy.

The same destructive operation runs after an ordinary keyboard interrupt and after any unhandled exception. A routine process termination is therefore treated as an account-w ...[truncated 1312 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove account-wide liquidation from the routine finally block. Routine shutdown should cancel or close only explicitly owned resources, then disconnect.
  • Assign a unique orderRef or equivalent ownership marker to every order created by the Skill.
  • Persist permanent order IDs and reconcile them with current positions before performing any close operation.
  • Limit shutdown handling to positions demonstrably created by the current strategy and account.
  • Make account-wide liquidation a separate emergency command requiring explicit confirmation and, for live accounts, an additional authorization control.
  • Cancel outstanding child orders before closing a Skill-owned position to prevent later duplicate or reversing fills.
  • Add tests with mixed manual and Skill-owned positions to verify that unrelated positions are never modified.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/trader.py:204
Finding

Position-Size Modifier Bypasses the Configured Per-Trade Risk Limit

Content
View full analysis

Vulnerability Details

File Location: scripts/risk_manager.py:123-137; scripts/decision_engine.py:221-224; scripts/trader.py:204-207
Vulnerability Type: Risk-limit bypass caused by unsafe operation ordering
Risk Level: High

Vulnerable Code

The risk manager first calculates the maximum quantity allowed by the configured risk percentage:

python
def size_position(self, signal: Signal, account_value: float) -> int:
    """
    Calculate share quantity using fixed fractional risk:
        shares = (account_value * max_risk_pct) / |entry - stop|
    """
    if account_value <= 0:
        return 0

    risk_per_share = abs(signal.entry_price - signal.stop_price)
    if risk_per_share <= 0:
        return 0

    max_risk_dollars = account_value * (self.risk["max_risk_per_trade_pct"] / 100)
    quantity = int(max_risk_dollars / risk_per_share)

    logger.debug(
        f"Position size for {signal.symbol}: "
        f"acct={account_value:.0f}, risk_$={max_risk_dollars:.2f}, "
        f"risk/share={risk_per_share:.4f}, qty={quantity}"
    )
    return max(quantity, 0)

The decision engine then permits a multiplier greater than one:

python
if final_confidence >= 0.75:
    position_size_modifier = min(position_size_modifier * 1.2, 1.5)  # Up to 150%
elif final_confidence < 0.55:
    position_size_modifier *= 0.75

The caller applies that multiplier after the maximum-risk quantity has been calculated:

python
quantity = risk_manager.size_position(signal, account_value)
quantity = int(quantity * decision.position_size_modifier)
if quantity <= 0:
    logger.info(f"Adjusted position size 0 for {signal.symbol} — skipping.")
    continue

Technical Analysis

size_position() calculates a quantity at the configured risk ceiling. The main loop subsequently multiplies that quantity by as much as 1.5 without repeating risk validation.

Consequent ...[truncated 1316 chars]

Remediation
View remediation

Remediation Suggestions

  • Apply confidence and news modifiers to a risk budget before calculating the final quantity.

  • Never allow a modifier to increase the risk budget above max_risk_per_trade_pct.

  • After all modifiers and integer rounding, recalculate:

    python
    final_risk = quantity * abs(signal.entry_price - signal.stop_price)
    

    Reject or reduce the order if final_risk exceeds the configured dollar-risk ceiling.

  • Add independent limits for maximum shares, maximum order notional, total portfolio exposure, and available buying power.

  • Revalidate stop price, entry price, quantity, and risk immediately before placeOrder().

  • Add tests proving that every modifier value, including 1.5, still results in risk at or below the configured maximum.

T08 · Insecure Dependencies

Warning
Location
SKILL.md:25
Finding

Unpinned Third-Party Dependencies Create a Supply-Chain Exposure

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:25-26; SKILL.md:187-190; references/ibkr_connection.md:10-12
Vulnerability Type: Uncontrolled dependency resolution without version or integrity pinning
Risk Level: Medium

Vulnerable Code

bash
pip install ib_insync pandas numpy pyyaml loguru
bash
pip install feedparser  # Only new dependency

The connection reference repeats the unpinned installation pattern:

bash
pip install ib_insync

Technical Analysis

The documented installation commands request mutable package names without exact versions, hashes, a lock file, or an explicitly trusted package index. Installation and subsequent imports can execute package-controlled code in the same environment as the trading application.

This process has access to local files, the active IBKR connection, account information, and brokerage order APIs. A malicious package release, dependency compromise, dependency-confusion event, or incompatible future update would therefore execute in a high-impact context.

No evidence shows that the currently named packages are malicious. The confirmed issue is the unsafe dependency acquisition method.

Attack Path

  1. An operator follows the documented pip install commands.
  2. The package resolver selects the latest package versions and transitive dependencies available from its configured index.
  3. A compromised, substituted, or otherwise unsafe release is downloaded because no approved version or hash is enforced.
  4. Package installation or import executes attacker-controlled code.
  5. That code runs with the trader process's filesystem and brokerage-session privileges.

Impact Assessment

A compromised dependency could read or alter local trading state, modify signals, submit or cancel brokerage orders, access account data, or tamper with logs. The effective scope is the Python environment and all privileges available to the ...[truncated 16 chars]

Remediation
View remediation

Remediation Suggestions

  • Provide a reviewed dependency manifest with exact versions.

  • Generate a hash-locked requirements file and install with hash enforcement, for example:

    bash
    pip install --require-hashes -r requirements.lock
    
  • Pin and review transitive dependencies, not only direct dependencies.

  • Document and enforce the intended package index.

  • Install dependencies in an isolated virtual environment under a minimally privileged operating-system account.

  • Use automated vulnerability and provenance scanning during release preparation.

  • Establish a controlled dependency-update process with testing before new versions reach a brokerage-enabled environment.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/trader.py:60
Finding

Configuration Schema Mismatch Prevents the Default Safety-Gated Connection

Content
View full analysis

Vulnerability Details

File Location: scripts/trader.py:60-61; config/settings.yaml:6-17
Vulnerability Type: Invalid configuration-key access causing startup failure
Risk Level: Low

Vulnerable Code

The connection function reads trading ports from the ibkr section:

python
safety = cfg["safety"]
live = safety["enable_live_trading"]
port = cfg["ibkr"]["live_trading_port"] if live else cfg["ibkr"]["paper_trading_port"]
host = cfg["ibkr"]["host"]
client_id = cfg["ibkr"]["client_id"]

The supplied configuration places those keys under safety instead:

yaml
ibkr:
  host: "127.0.0.1"
  port: 7497
  client_id: 1
  timeout: 20

safety:
  enable_live_trading: false
  paper_trading_port: 7497
  live_trading_port: 7496
  require_confirmation: true

Technical Analysis

With the shipped configuration, accessing cfg["ibkr"]["paper_trading_port"] raises KeyError before the connection attempt. Enabling live trading similarly causes access to the missing cfg["ibkr"]["live_trading_port"].

The exception occurs before the try block surrounding ib.connect(), so connect_ibkr() does not convert it into a controlled connection failure. This contradicts the documented Quick Start behavior and can encourage unsafe ad hoc configuration changes when users attempt to repair the application.

Attack Path

  1. An operator starts the application using the supplied config/settings.yaml.
  2. connect_ibkr() evaluates the selected port.
  3. The function looks for paper_trading_port or live_trading_port under ibkr.
  4. The key does not exist because both values are under safety.
  5. A KeyError terminates startup before a controlled connection or safety-mode verification occurs.

Impact Assessment

The direct impact is loss of availability: the default application cannot reach its trading loop. No additional account privileges are obtained through this de ...[truncated 159 chars]

Remediation
View remediation

Remediation Suggestions

  • Read the ports from their actual section:

    python
    port = (
        safety["live_trading_port"]
        if live
        else safety["paper_trading_port"]
    )
    
  • Alternatively, move both port keys into ibkr and update all documentation consistently.

  • Define and validate a configuration schema before initializing logging, network access, or order components.

  • Fail closed with a clear error if mode, port, host, or confirmation settings are absent or invalid.

  • Add startup tests for both paper and live configurations.

  • Verify after connection that the selected IBKR account type matches the requested mode rather than relying only on the port number.

Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose centers on IBKR connectivity and autonomous paper-trading workflows, but the described behavior also includes external Google News RSS fetching, sentiment analysis, and local news logging that are not clearly represented in the top-level purpose. This mismatch can mislead users and orchestration systems about what the skill actually does, causing it to be invoked in contexts where unexpected network access, data collection, or decision influence occurs.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger rules are extremely broad, including generic terms like 'trade' and applying the skill to 'any mention of automated trading' or even exploratory requests. Overbroad triggers can cause inappropriate automatic activation in sensitive finance contexts, increasing the chance that the agent performs networked analysis, stores data, or steers user decisions when the user did not intend to invoke this specific high-impact skill.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/ibkr_connection.md (reported line 59)May include surrounding context.

Store credentials in .env, never hardcode:

bash
# .env
IBKR_HOST=127.0.0.1
IBKR_PORT=7497
IBKR_CLIENT_ID=1

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The close_all_positions method issues market-close orders for every open position immediately, with no confirmation gate, dry-run mode, account/environment check, or per-position validation. In an autonomous IBKR trading skill, this is especially dangerous because any accidental invocation, logic bug, or prompt-driven misuse can liquidate an entire paper or live account at market prices, causing broad unintended execution and potential financial loss.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The finally block unconditionally calls execution_engine.close_all_positions() on any exit path, including unhandled exceptions, transient errors, or operator interruption. In an autonomous trading skill, this can trigger liquidation of every open position without an explicit user-facing confirmation or a narrowly scoped policy check, creating substantial operational and financial risk even if the system was only meant to stop the bot rather than flatten the account.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill advertises capabilities that imply network access and persistent file writes, but it does not declare any explicit tool scope or permissions boundary. In an autonomous trading context, undeclared network and file-write behavior reduces operator visibility and consent, making it easier for the skill to fetch external data or persist sensitive operational history without clear governance.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The safety note emphasizes live-trading disablement but omits other impactful behaviors such as persistent storage of trade/news history and external news fetching/logging. In a trading assistant, incomplete disclosure can create a false sense of safety, leading users to overlook privacy, data retention, and external-influence risks that materially affect how the system operates.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The documentation tells users to disable IBKR's Read-Only API mode to place orders, but it does not prominently warn about the risk of accidentally sending real trades, especially since the same document also lists live-trading ports. In an autonomous trading skill, that omission increases the chance of unsafe deployment or operator error leading to unintended order execution and financial loss.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documentation provides code that places, modifies, and cancels broker orders against IBKR without any prominent warning that these calls affect brokerage state and may execute trades if connected to a live account. In an autonomous trading skill, users may copy examples directly or an agent may operationalize them, increasing the risk of unintended order submission, position changes, or financial loss.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This markdown file includes a rollback example that writes to memory/strategies.json and instructs the operator to manually update config/settings.yaml, both of which can alter persisted configuration and system behavior. The section does not include any warning about overwriting existing state, backing up files, or validating the target version before applying the change.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The Google News RSS URL hard-codes hl=en-US, gl=US, and ceid=US:en, which forces both language and region selection. This is a natural-language/locale policy concern because the skill does not offer any user choice or documented justification for restricting results to US English.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The self-improvement path automatically mutates strategy parameters on disk and immediately pushes them into the live strategy engine with no approval gate, rollback check, or safety validation. In an autonomous trading skill, this creates a dangerous feedback loop where poor metrics, corrupted history, or logic errors can silently degrade execution behavior and cause repeated bad trades or uncontrolled strategy drift.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The method persists modified decision weights to strategies.json, which changes stored skill state on disk. Although there is an internal comment and error logging, there is no user-facing disclosure or confirmation around this file write operation in the code shown.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code appends fetched news content, including headlines, summaries, URLs, and later reaction/result fields, to memory/news_log.json. Although the docstring states that logging occurs, there is no confirmation prompt or user-facing notice at write time that persistent local storage is being updated.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.