T09 · Insecure Skill Coding Practices
- Location
- references/secure-storage.md:145
- Finding
- Shell Command Injection in the TypeScript 1Password Integration<![CDATA[ ## Vulnerability Details **File Location**: `references/secure-storage.md:145-162` **Vulnerability Type**: OS command injection through shell interpolation **Risk Level**: High ### Vulnerable Code ```typescript import { execSync } from 'child_process'; interface SessionKey { sessionKey: string; smartAccount: string; chainId: number; expires: string; spendingLimit: string; allowedContracts: string[]; allowedMethods: string[]; } function getSessionKey(itemName: string): SessionKey { const vault = "Agent-Credentials"; const output = execSync( `op item get "${itemName}" --vault "${vault}" --format json`, { encoding: 'utf-8', timeout: 30000 } ); ``` ### Technical Analysis The `itemName` parameter is inserted directly into a command string passed to `execSync`. In this form, Node.js invokes a shell to parse the command. Wrapping the value in double quotes does not make it safe: shell metacharacters, embedded quotes, and command substitution syntax can escape or execute within the quoted context. If an untrusted prompt, API parameter, configuration value, or agent-generated value can influence `itemName`, an attacker can cause the process to run additional operating-system commands. This is especially sensitive because the function is intended to run in a process authenticated to 1Password and handling wallet session credentials. ### Attack Path 1. An attacker gains control over, or influences, the `itemName` value passed to `getSessionKey`. 2. The attacker supplies a value containing shell syntax, such as an embedded quote followed by a command and comment marker. 3. The value is interpolated into the command string. 4. `execSync` passes the resulting string to the shell. 5. The shell interprets the injected syntax and executes the attacker's command. 6. The injected process inherits the agent's operating-system identity, environment, filesystem access, and potentially its authenticated 1Password session. ### Impact ...[truncated 818 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Avoid invoking a shell. Pass each argument separately with `execFileSync`, `spawn`, or `spawnSync`: ```typescript import { execFileSync } from 'child_process'; function getSessionKey(itemName: string): SessionKey { const vault = 'Agent-Credentials'; if (!/^[A-Za-z0-9][A-Za-z0-9._ -]{0,127}$/.test(itemName)) { throw new Error('Invalid 1Password item name'); } const output = execFileSync( 'op', ['item', 'get', itemName, '--vault', vault, '--format', 'json'], { encoding: 'utf-8', timeout: 30000, shell: false, windowsHide: true, } ); // Parse and validate output. } ``` Additional hardening should include: 1. Use an application-controlled allowlist of permitted item identifiers rather than accepting arbitrary names. 2. Prefer immutable item UUIDs over display names. 3. Run the process under a dedicated, unprivileged service account. 4. Restrict the 1Password service account to read-only access to only the required session-key items. 5. Ensure master credentials remain in a vault inaccessible to the agent. 6. Avoid logging commands, item output, or exception objects that may contain sensitive data. 7. Add tests containing quotes, command substitutions, newlines, and shell operators to verify they are rejected or handled literally. ]]>
