Back to skill

Security audit

quantum-sim

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local quantum simulator, but documented inputs can consume extreme memory or CPU, so it should be reviewed before installation.

Install only if you are comfortable reviewing or constraining local simulations. Avoid high qubit counts and very large shot counts unless the script is fixed or run inside a memory- and CPU-limited environment.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/quantum_sim.py:104
Finding
Dense gate construction permits severe memory-exhaustion denial of service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/quantum_sim.py:104-132` **Vulnerability Type**: Uncontrolled memory allocation / algorithmic denial of service **Risk Level**: High ### Vulnerable Code ```python class QuantumCircuit: def __init__(self, n_qubits: int): if n_qubits < 1 or n_qubits > 20: raise ValueError("Qubit count must be 1-20") self.n = n_qubits self.dim = 1 << n_qubits # 2^n self.state = _zeros(self.dim) self.state[0] = 1+0j self.log: List[str] = [] def _apply_single(self, gate_mat, qubit: int): """Apply a single-qubit gate to the statevector.""" n = self.n if _USE_NUMPY: op = np.array([[1+0j]]) for i in range(n-1, -1, -1): if i == qubit: op = np.kron(op, np.array(gate_mat, dtype=complex)) else: op = np.kron(op, np.eye(2, dtype=complex)) self.state = op @ self.state else: op = [[1+0j]] for i in range(n-1, -1, -1): if i == qubit: op = _kron(op, gate_mat) else: op = _kron(op, _I2) self.state = _matmul_vec(op, self.state) ``` ### Technical Analysis The constructor accepts up to 20 qubits, for which the statevector contains `2^20` amplitudes. However, applying any single-qubit gate does not update those amplitudes directly. Instead, `_apply_single` constructs a complete dense `2^n × 2^n` operator through repeated Kronecker products. At 20 qubits, the resulting matrix contains `2^40` complex values. With NumPy `complex128` values, the final matrix alone would require approximately 16 TiB of memory, excluding temporary arrays produced by repeated `np.kron` operations. The pure-Python fallback has even greater object and list overhead. This behavior conflicts with the documented implication that a 20-qubit simulation ...[truncated 1780 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace dense operator construction with an in-place or bounded-copy statevector update. For a single-qubit gate, iterate over amplitude pairs whose indices differ only at the target-qubit bit: ```python def _apply_single(self, gate, qubit): step = 1 << qubit span = step << 1 for base in range(0, self.dim, span): for offset in range(step): i0 = base + offset i1 = i0 + step a0 = self.state[i0] a1 = self.state[i1] self.state[i0] = gate[0][0] * a0 + gate[0][1] * a1 self.state[i1] = gate[1][0] * a0 + gate[1][1] * a1 ``` This reduces auxiliary memory from `O(4^n)` to `O(1)` and performs `O(2^n)` arithmetic per gate. 2. Apply explicit resource budgeting before allocating the statevector. Calculate the required statevector size from the selected representation and reject requests exceeding a configurable memory allowance. 3. If dense matrix construction is retained, lower the maximum qubit count to a value proven safe for the runtime environment. This is inferior to direct statevector updates and should only be a temporary mitigation. 4. Add automated tests for the documented maximum qubit count and monitor peak resident memory, not merely the final statevector size. 5. Run simulations inside a process or container with enforceable memory and CPU limits so an unexpected allocation cannot exhaust the host. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/quantum_sim.py:190
Finding
Unbounded measurement sampling enables CPU-exhaustion denial of service<![CDATA[ ## Vulnerability Details **File Location**: `scripts/quantum_sim.py:190-205, 317-320, 375-378` **Vulnerability Type**: Unbounded user-controlled computation and ineffective resource-control flag **Risk Level**: Medium ### Vulnerable Code ```python def measure(self, shots: int = 1024) -> Dict[str, int]: probs = self.probabilities() counts: Dict[str, int] = {} for _ in range(shots): r = random.random() cumulative = 0.0 chosen = self.dim - 1 for i, p in enumerate(probs): cumulative += p if r < cumulative: chosen = i break bitstr = format(chosen, f'0{self.n}b') counts[bitstr] = counts.get(bitstr, 0) + 1 return dict(sorted(counts.items())) ``` ```python def print_results(qc: QuantumCircuit, shots: int, json_out: bool, label: str = ''): probs = qc.probabilities() counts = qc.measure(shots) sv = qc.statevector() ``` ```python parser.add_argument('--shots', type=int, default=1024) parser.add_argument('--json', action='store_true') parser.add_argument('--list-presets', action='store_true') parser.add_argument('--statevector-only', action='store_true') ``` ### Technical Analysis The `--shots` argument accepts an arbitrary Python integer without a positive lower bound or safe upper bound. `measure` performs an outer loop once per requested shot. For every shot, it may scan the complete probability array containing `2^n` entries. Its worst-case time complexity is therefore `O(shots × 2^n)`. A caller can request an extremely large number of shots and force the process to consume CPU for an impractical duration. Large circuits amplify the cost of each shot. The documented `--statevector-only` option is intended to skip measurement simulation, but the parsed value is never inspected. `print_results` always invokes `qc.measure(shots)`. Consequently, callers cannot use the advertised option to avoid this expensive operation. Neg ...[truncated 1999 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a positive, configurable upper bound for `--shots` immediately after parsing: ```python MAX_SHOTS = 1_000_000 if args.shots < 1 or args.shots > MAX_SHOTS: parser.error(f"--shots must be between 1 and {MAX_SHOTS}") ``` 2. Implement the documented `--statevector-only` behavior. Pass the option to the output function and do not call `measure` when it is enabled: ```python def print_results(qc, shots, json_out, label='', statevector_only=False): probs = qc.probabilities() counts = {} if statevector_only else qc.measure(shots) sv = qc.statevector() ``` 3. Improve sampling complexity. Build a cumulative probability distribution once and use binary search for each random value, reducing sampling from worst-case `O(shots × 2^n)` to approximately `O(2^n + shots × n)`. If NumPy is available, use a bounded vectorized sampling operation. 4. Apply a combined workload budget based on both the number of shots and the state dimension rather than validating each independently. 5. Add execution timeouts and CPU limits at the hosting-process or container level as defense in depth. 6. Add tests confirming that excessive and negative shot counts are rejected and that `--statevector-only` never invokes measurement. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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 (3)

Vague Triggers

Medium
Confidence
92% confidence
Finding
The skill advertises trigger phrases including broad, common terms such as "qubit" and "superposition," which may appear in general educational or conceptual discussions that do not actually request execution of this simulator. Overly broad activation can cause unintended invocation, leading to confusing behavior, incorrect tool routing, and increased exposure of any downstream tool functionality beyond the user's intent.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
elif qc is None:
            raise ValueError("First instruction must be: qubits N")
        elif op in ('h','x','y','z','s','t','sdg','tdg'):
            getattr(qc, op)(int(parts[1]))
        elif op in ('rx','ry','rz','p'):
            getattr(qc, op)(float(parts[1]), int(parts[2]))
        elif op in ('cx','cnot'):
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
elif op in ('h','x','y','z','s','t','sdg','tdg'):
            getattr(qc, op)(int(parts[1]))
        elif op in ('rx','ry','rz','p'):
            getattr(qc, op)(float(parts[1]), int(parts[2]))
        elif op in ('cx','cnot'):
            qc.cx(int(parts[1]), int(parts[2]))
        elif op == 'cz':
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.