Back to skill

Security audit

Twenty CRM

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Twenty CRM helper, but it needs Review because it can change or delete CRM data and has weak safety controls around credentials and configuration.

Install only if you are comfortable giving this skill API-token authority over your Twenty CRM. Use a narrowly scoped token, prefer HTTPS, avoid delete permissions unless needed, review the hardcoded config path before use, and treat delete/destroy helpers as potentially irreversible.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/twenty-config.sh:7
Finding
Arbitrary Shell Command Execution Through Sourced Configuration File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/twenty-config.sh:7-11` **Vulnerability Type**: Unsafe execution of configuration as shell code **Risk Level**: High ### Vulnerable Code ```bash CONFIG_FILE="/Users/jhumanj/clawd/config/twenty.env" if [ -f "$CONFIG_FILE" ]; then # shellcheck disable=SC1090 source "$CONFIG_FILE" fi ``` ### Technical Analysis The script uses Bash `source` to load `twenty.env`. This operation does not merely parse environment-variable assignments; it executes the entire file as shell code in the context of the calling process. Consequently, command substitutions, function definitions, redirections, and arbitrary commands placed in the configuration file are executed whenever any REST or GraphQL helper imports `twenty-config.sh`. The file location is also hardcoded outside the project directory and conflicts with the project documentation, which describes `config/twenty.env` as the expected location. This makes configuration provenance and permission management less predictable. ### Attack Path 1. An attacker obtains write access to `/Users/jhumanj/clawd/config/twenty.env`, directly or through another local vulnerability or insecure file permissions. 2. The attacker inserts a shell payload, for example: ```bash TWENTY_BASE_URL=https://crm.example.com TWENTY_API_KEY=example arbitrary_attacker_command ``` 3. The victim invokes any REST or GraphQL helper in the Skill. 4. The helper sources `twenty-config.sh`. 5. Bash sources `twenty.env` and executes the attacker's command with the victim's privileges. ### Impact Assessment Successful exploitation permits arbitrary command execution with the privileges of the user or Agent running the Skill. The attacker could read or modify files accessible to that account, steal the Twenty API key and other credentials, alter CRM requests, or execute additional local programs. The issue does not independently provide elevated operating-system privileges; ...[truncated 68 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not use `source` to read a data-only configuration file. - Parse only an explicit allowlist of keys, such as `TWENTY_BASE_URL` and `TWENTY_API_KEY`. - Reject malformed lines, duplicate keys, command substitutions, shell metacharacters, and unexpected variables. - Use a documented project-relative or explicitly user-configurable path rather than a developer-specific absolute path. - Verify that the configuration file is a regular file, is owned by the expected user, and is not writable by group or other users. - Prefer credentials supplied through a protected process environment or operating-system secret store. A safe implementation can parse each line as data without evaluating it as Bash code. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/twenty-graphql.sh:26
Finding
Bearer Token and CRM Data Can Be Transmitted Over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/twenty-graphql.sh:26-30` **Vulnerability Type**: Plaintext transmission of authentication credentials and sensitive data **Risk Level**: Medium ### Vulnerable Code ```bash curl -sS -X POST "$TWENTY_BASE_URL/graphql" \ -H "Authorization: Bearer $TWENTY_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ --data "$BODY" ``` The same credential-transmission pattern appears in: - `scripts/twenty-rest-get.sh:24-26` - `scripts/twenty-rest-post.sh:18-22` - `scripts/twenty-rest-patch.sh:18-22` - `scripts/twenty-rest-delete.sh:24-26` The project documentation explicitly permits an HTTP base URL: ```markdown - `TWENTY_BASE_URL` (e.g. `https://crm.example.com` or `http://localhost:3000`) ``` ### Technical Analysis Every API operation places the Twenty API key in an HTTP Authorization header. No validation requires `TWENTY_BASE_URL` to use HTTPS or restricts plaintext HTTP to loopback addresses. Although HTTP can be appropriate for a strictly local loopback development service, the implementation accepts any HTTP destination. If a non-loopback HTTP server is configured, the bearer token, request bodies, CRM records, and server responses traverse the network without transport encryption or server authentication. Because bearer tokens grant access based on possession, an intercepted token can generally be replayed without knowing another secret. ### Attack Path 1. A legitimate configuration uses a remote `http://` CRM endpoint, or an attacker with configuration influence changes `TWENTY_BASE_URL` to one. 2. The victim invokes a REST or GraphQL helper. 3. The script sends `Authorization: Bearer $TWENTY_API_KEY` and potentially sensitive CRM data over plaintext HTTP. 4. A network observer, compromised gateway, or man-in-the-middle captures the request. 5. The attacker extracts and replays the bearer token against the CRM API until the token expires or is revoked. ## ...[truncated 503 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `https://` for all non-loopback base URLs. - If local development over HTTP is necessary, permit it only after validating that the hostname is a loopback address such as `127.0.0.1`, `::1`, or `localhost`. - Reject remote plaintext HTTP URLs with a clear error before sending any credentials. - Continue using curl's default TLS certificate verification and do not introduce insecure options such as `--insecure`. - Document secure certificate and reverse-proxy configuration for self-hosted Twenty instances. - Use narrowly scoped, short-lived API tokens and provide token-rotation guidance in case transport exposure is suspected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/twenty-create-company.sh:18
Finding
Predictable Temporary File Enables Symlink-Based File Clobbering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/twenty-create-company.sh:18-32` **Vulnerability Type**: Unsafe predictable temporary-file creation **Risk Level**: Medium ### Vulnerable Code ```bash python3 - <<'PY' "$NAME" "$DOMAIN" "$EMPLOYEES" > /tmp/twenty_create_company.json import json,sys name,domain,employees = sys.argv[1],sys.argv[2],sys.argv[3] payload = {"name": name} if domain: payload["domainName"] = domain if employees: try: payload["employees"] = int(employees) except ValueError: payload["employees"] = employees print(json.dumps(payload)) PY "$SCRIPT_DIR/twenty-rest-post.sh" "/companies" "$(cat /tmp/twenty_create_company.json)" ``` ### Technical Analysis The script writes generated JSON to a fixed filename in the shared `/tmp` directory. Shell output redirection opens the destination without exclusive creation and follows symbolic links. An attacker who can create `/tmp/twenty_create_company.json` before the victim runs the script can replace it with a symbolic link to another file writable by the victim. The redirection then truncates and overwrites the linked target. The temporary file is also not removed after use. Depending on the invoking user's `umask` and platform-specific `/tmp` protections, this can leave company input data accessible on disk and creates race conditions between concurrent invocations. ### Attack Path 1. A local attacker predicts the fixed filename `/tmp/twenty_create_company.json`. 2. The attacker creates that path as a symbolic link to a target file that the victim account can modify. 3. The victim invokes `twenty-create-company.sh`. 4. Bash follows the symbolic link while processing the output redirection. 5. The target is truncated and overwritten with the generated JSON payload. 6. The script subsequently reads from the same attacker-controlled path, allowing local interference with the CRM request as well. ### Impact Assessment The attacker can overwrite files writable by the ...[truncated 409 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Avoid creating a temporary file and capture the generated JSON directly: ```bash PAYLOAD=$(python3 - "$NAME" "$DOMAIN" "$EMPLOYEES" <<'PY' import json import sys name, domain, employees = sys.argv[1:4] payload = {"name": name} if domain: payload["domainName"] = domain if employees: try: payload["employees"] = int(employees) except ValueError: payload["employees"] = employees print(json.dumps(payload)) PY ) "$SCRIPT_DIR/twenty-rest-post.sh" "/companies" "$PAYLOAD" ``` If a file is operationally required: - Create it with `mktemp` in a protected directory. - Set a restrictive `umask`, such as `077`. - Install an `EXIT` trap to remove the file. - Do not reuse a fixed shared path. - Ensure creation is exclusive and reject symbolic links. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (9)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill exposes shell-based capabilities that can make authenticated REST and GraphQL calls to a self-hosted CRM, but it does not declare any tool scope or allowed-tools restrictions. Without explicit scoping, an agent may invoke broader shell functionality than intended, increasing the chance of unauthorized data access, modification, or destructive actions against the CRM.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation advertises a delete helper for CRM objects without any cautionary note, confirmation guidance, or safety constraints. In a CRM context this is materially risky because records may contain important business data, and an agent or user could invoke destructive deletion without understanding permanence, backup requirements, or approval expectations.

External Transmission

Medium
Category
Data Exfiltration
Content
BODY=$(printf '{"query":%s}' "$QUERY_JSON")
fi

curl -sS -X POST "$TWENTY_BASE_URL/graphql" \
  -H "Authorization: Bearer $TWENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This shell script performs an HTTP DELETE request, and may target an even more destructive '/destroy' endpoint, but it provides no confirmation prompt, warning message, or visible user disclosure before executing the operation. The brief usage comments describe syntax but do not warn about deletion impact, which increases the risk of accidental destructive use.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script performs an outbound HTTP request with curl and includes the TWENTY_API_KEY bearer token in the Authorization header. While the code is straightforward, there is no confirmation prompt, user-facing log/message, or inline warning explaining that a credential-backed network request will be made.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

curl -sS -X PATCH "$TWENTY_BASE_URL/rest${PATH_PART}" \
  -H "Authorization: Bearer $TWENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script performs a network PATCH request and sends user-supplied JSON along with a bearer token, but it provides no confirmation prompt, visible disclosure, or explanatory comment warning that data will be transmitted to a remote service. This is a safety-relevant operation for a code file because it modifies remote state and uses sensitive credentials.

External Transmission

Medium
Category
Data Exfiltration
Content
exit 1
fi

curl -sS -X POST "$TWENTY_BASE_URL/rest${PATH_PART}" \
  -H "Authorization: Bearer $TWENTY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This shell script performs a network POST request using user-provided JSON and includes an Authorization bearer token from configuration, but the file provides no confirmation prompt, warning message, or explanatory comment about transmitting data off-host. For a code file, this is a safety-relevant external data transmission and credential use with no visible user disclosure in the script itself.

Static analysis

No suspicious patterns detected.