Back to skill

Security audit

Propel Code Review Smoke

Security checks for vulnerabilities and agentic risk

Overview

The skill performs a coherent code-review integration, but it handles API credentials and source diffs in ways users should review carefully before installing.

Install only if you are comfortable uploading reviewed diffs to Propel and granting a reviews:read/reviews:write token. Prefer setting PROPEL_API_KEY outside chat for the current session or a proper secret store, avoid saving it in shell profiles, do not run with untrusted PROPEL_API_BASE_URL/PROPEL_API_URL values, and replace the documented fixed /tmp diff path with a private mktemp file that is removed after use.

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/create_review.sh:36
Finding
Unvalidated API Endpoint Override Exposes Bearer Tokens and Source Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/create_review.sh:36, 79-80, 130-151`; `scripts/poll_review.sh:35, 105-106, 154-168`; `scripts/post_comment_feedback.sh:32, 67-68, 120-128`; `scripts/smoke_test_permissions.sh:35, 53-54, 137-146` **Vulnerability Type**: Arbitrary credential and sensitive-data transmission endpoint **Risk Level**: High ### Vulnerable Code The review creation script permits the API endpoint to be supplied through environment variables or a command-line argument: ```bash API_URL="${PROPEL_API_BASE_URL:-${PROPEL_API_URL:-https://api.propelcode.ai}}" ``` ```bash --api-url) API_URL="$(require_option_value "$1" "${2-}")" shift 2 ;; ``` It subsequently attaches the bearer token and sends the complete Git diff to that endpoint: ```bash BODY_FILE="$(mktemp)" CURL_CONFIG_FILE="$(mktemp)" chmod 600 "$CURL_CONFIG_FILE" trap 'rm -f "$BODY_FILE" "$CURL_CONFIG_FILE"' EXIT printf 'header = "Authorization: Bearer %s"\n' "$PROPEL_API_KEY" >"$CURL_CONFIG_FILE" printf 'header = "Content-Type: application/json"\n' >>"$CURL_CONFIG_FILE" HTTP_CODE="" for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do if ! HTTP_CODE="$( jq -n \ --rawfile diff "$DIFF_FILE" \ --arg repo "$REPO_SLUG" \ --arg base "$BASE_COMMIT" \ --arg head "$HEAD_COMMIT_SHA" \ --arg branch "$BRANCH_NAME" \ '({diff:$diff, repository:$repo, base_commit:$base} + (if $head != "" then {head_commit_sha:$head} else {} end) + (if $branch != "" then {branch:$branch} else {} end))' \ | curl -sS -o "$BODY_FILE" -w "%{http_code}" \ --config "$CURL_CONFIG_FILE" \ --data-binary @- \ "$API_URL/v1/reviews" )"; then ``` The polling and feedback scripts use the same endpoint-override pattern and attach the same credential: ```bash API_URL="${PROPEL_API_BASE_URL:-${PROPEL_API_URL:-https://api.propelcode.ai}}" ``` ```bash printf 'header = "Authorization: Bearer %s"\n' "$PROPEL_API_KEY ...[truncated 2493 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove endpoint overrides from production-facing helpers unless they are strictly required. 2. Enforce the canonical endpoint: ```bash API_URL="https://api.propelcode.ai" ``` 3. If custom endpoints are required for development, parse and validate the URL before creating the authorization header: - Require the `https` scheme. - Allowlist exact approved hostnames. - Reject embedded credentials, unexpected ports, fragments, and malformed URLs. - Do not rely on suffix matching such as `*.propelcode.ai` unless every subdomain is trusted. 4. Require an explicit, interactive security confirmation before sending credentials to a non-production endpoint. 5. Use separate test credentials with minimal scopes for development and smoke testing. 6. Apply the same validation consistently to `create_review.sh`, `poll_review.sh`, `post_comment_feedback.sh`, and `smoke_test_permissions.sh`. 7. Document that endpoint-related environment variables are security-sensitive and must not be inherited from untrusted CI jobs or repository configuration. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:42
Finding
API Token Is Collected Through Chat and Persisted in Plaintext Shell Profiles<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:42-64` **Vulnerability Type**: Insecure credential collection, plaintext storage, and unsafe command interpolation **Risk Level**: High ### Vulnerable Code The Skill explicitly asks the user to place a live credential in the conversation: ```text Message to user: > `PROPEL_API_KEY` is not set. Opening the token creation page: > https://app.propelcode.ai/administration/settings?tab=review-api-tokens&token_name=Claude+Code&scopes=reviews:read,reviews:write > The name and scopes are pre-filled. Click **Create token**, copy it, and paste it here. ``` It then validates only a prefix: ```text Step 2 — Wait for the user to paste the token. Do not proceed until the user pastes a value starting with `rev_`. If the value doesn't start with `rev_`, tell them it doesn't look valid and ask them to try again. ``` Finally, it instructs the agent to interpolate and persist that value in a shell startup file: ```bash case "$SHELL" in */zsh) SHELL_RC="$HOME/.zshrc" ;; */bash) SHELL_RC="$HOME/.bashrc" ;; *) SHELL_RC="" ;; esac; if [ -z "$SHELL_RC" ] && [ -f "$HOME/.zshrc" ]; then SHELL_RC="$HOME/.zshrc"; fi; if [ -z "$SHELL_RC" ] && [ -f "$HOME/.bashrc" ]; then SHELL_RC="$HOME/.bashrc"; fi; if [ -n "$SHELL_RC" ]; then printf '\n# Propel Review API token\nexport PROPEL_API_KEY="%s"\n' "<TOKEN>" >> "$SHELL_RC" && echo "Saved to $SHELL_RC"; else echo "No shell profile found"; fi; export PROPEL_API_KEY="<TOKEN>" ``` ### Technical Analysis Conversation channels are not appropriate secret-entry mechanisms. A token pasted into chat may be retained in conversation history, audit logs, telemetry, screenshots, exports, or other systems involved in processing agent messages. The persistence instruction writes the token in plaintext to `.zshrc` or `.bashrc`. These files are long-lived, commonly backed up, and may be read by unrelated local processes operating under the same account. Every future interactive shell also i ...[truncated 2183 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never ask users to paste credentials into the conversation. 2. Obtain the token through a secure mechanism, such as: - A hidden interactive prompt using `read -rs`. - An operating-system credential manager. - A supported secret-management service. - A preconfigured environment variable supplied outside the agent conversation. 3. Avoid writing API keys to `.bashrc`, `.zshrc`, repository files, or other plaintext startup configuration. 4. Store persistent credentials in an OS keychain or secret manager with restrictive access controls. 5. Keep the credential scoped to the process that requires it rather than exporting it to every future shell. 6. If a file must be used, create a dedicated mode-`0600` secret file outside the repository and load it only for the relevant command. 7. Never construct a shell command by textual replacement of a secret. Pass values as environment variables or positional arguments through an execution API that does not invoke shell parsing. 8. Validate the full token format and length, not only the `rev_` prefix. Format validation must supplement—not replace—safe parameter handling. 9. Provide token revocation and rotation instructions in case a token has already been pasted into a conversation or stored in a shell profile. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:228
Finding
Predictable Shared Temporary Diff Path Enables Symlink Overwrite and Source Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:228, 255` **Vulnerability Type**: Unsafe predictable temporary file **Risk Level**: Medium ### Vulnerable Code The recommended workflow writes a potentially sensitive Git diff to a fixed path in the shared temporary directory: ```bash git diff "$BASE_BRANCH" > /tmp/review_api.diff ``` The same fixed path is used in the complete production example: ```bash BASE_BRANCH=$(gh pr view --json baseRefName -q '.baseRefName' 2>/dev/null || git remote show origin | sed -n '/HEAD branch/s/.*: //p') BASE_COMMIT=$(git rev-parse "$BASE_BRANCH") HEAD_COMMIT=$(git rev-parse HEAD) BRANCH=$(git rev-parse --abbrev-ref HEAD) REPO_SLUG=$(git remote get-url origin | sed -E 's#(git@github.com:|https://github.com/)##; s/\.git$//') git diff "$BASE_BRANCH" > /tmp/review_api.diff CREATE_RESPONSE=$( scripts/create_review.sh \ --diff-file /tmp/review_api.diff \ --repo "$REPO_SLUG" \ --base-commit "$BASE_COMMIT" \ --head-commit-sha "$HEAD_COMMIT" \ --branch "$BRANCH" ) ``` ### Technical Analysis `/tmp` is generally shared among local users. The fixed filename `/tmp/review_api.diff` can be predicted and pre-created. Standard shell redirection follows symbolic links, so if the path is a symlink, the command opens and truncates the symlink target using the invoking user's permissions. The resulting file's permissions depend on the user's umask. The documented workflow neither creates it atomically with restrictive permissions nor removes it afterward. The file can therefore retain proprietary source changes after review submission. The scripts themselves generally use `mktemp`; the weakness is in the documented and recommended invocation workflow. ### Attack Path #### Symlink overwrite 1. A local attacker predicts that the user will run the documented workflow. 2. The attacker creates `/tmp/review_api.diff` as a symbolic link to a file writable by the victim. 3. The victim executes: ```bash ...[truncated 1195 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the fixed path with an atomically created temporary file: ```bash DIFF_FILE="$(mktemp "${TMPDIR:-/tmp}/propel-review.XXXXXX")" chmod 600 "$DIFF_FILE" trap 'rm -f "$DIFF_FILE"' EXIT git diff "$BASE_BRANCH" --no-color >"$DIFF_FILE" ``` 2. Pass `"$DIFF_FILE"` to `create_review.sh` rather than `/tmp/review_api.diff`. 3. Delete the file immediately after successful submission if it is no longer needed. 4. Set a restrictive umask before creating any file containing source code: ```bash umask 077 ``` 5. Avoid reusable or predictable temporary filenames in all examples and recommended workflows. 6. Consider piping the diff directly into a helper designed to consume standard input, eliminating the intermediate file when practical. ]]>
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
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code chunk is limited to review creation via POST to /v1/reviews using a diff file, repository slug, and base commit, then returning the API response containing a review_id. It includes argument parsing, validation, retries, and error handling, which are supporting details and consistent with submission. However, the declared description claims a broader workflow: asynchronous reviews plus polling, findings retrieval, and comment feedback. None of those later-stage capabilities appear in this code. Therefore the description overstates what this supplied code chunk actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description presents a broader end-to-end capability: running async diff-based code reviews with Propel, polling for completion, retrieving findings, and sending feedback comments. The supplied code chunk is much narrower. It accepts a review ID, authenticates with PROPEL_API_KEY, repeatedly GETs /v1/reviews/<id>, respects poll timing hints, handles retries/timeouts, and emits the final JSON when status becomes completed or failed. This aligns with only the polling portion of the description. There is no code to generate or submit diffs, start a review job, parse findings into comments, or post feedback. Because the actual behavior is a subset of the declared purpose and omits key stated capabilities, the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description presents a full review workflow skill: submit diffs for async review, poll until complete, fetch structured findings, and send comment feedback. The supplied code chunk instead is a standalone smoke-test script focused on permission behavior of the /v1/reviews endpoint. It creates three test cases using valid/invalid repo and token combinations and checks for expected status codes. While it does submit a diff to the review endpoint, that is only in service of permission testing, not actual review orchestration. Key declared behaviors—polling, result retrieval, and comment feedback—are absent, and the code's primary purpose is materially different from the description.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The skill instructs the agent to append the API token to the user's shell profile, creating long-lived credential persistence on disk. This materially increases exposure because shell startup files are broadly accessible to local tools, may be backed up or synced, and can unintentionally leak secrets through logs, history, or later automation.

Ae1

High
Category
analysis-evasion
Content
- `scripts/poll_review.sh` wraps polling `GET /v1/reviews/:review_id` until
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/poll_review.sh` wraps polling `GET /v1/reviews/:review_id` until
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/poll_review.sh` wraps polling `GET /v1/reviews/:review_id` until
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/poll_review.sh` wraps polling `GET /v1/reviews/:review_id` until
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/poll_review.sh` wraps polling `GET /v1/reviews/:review_id` until
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill clearly instructs use of shell commands, browser launching, git, and helper scripts, but it does not declare any explicit tool scope or allowed-tools boundary. That increases the chance an agent executes broader local commands than intended, especially because the workflow includes filesystem writes and networked API interactions.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The skill directs the agent to open a browser to a credential-generation page and guide the user through obtaining a token. That expands the skill from code review into credential acquisition, which is sensitive behavior and can normalize phishing-like flows or unnecessary secret handling by the agent.

Session Persistence

Medium
Category
Rogue Agent
Content
Message to user:
> `PROPEL_API_KEY` is not set. Opening the token creation page:
> https://app.propelcode.ai/administration/settings?tab=review-api-tokens&token_name=Claude+Code&scopes=reviews:read,reviews:write
> The name and scopes are pre-filled. Click **Create token**, copy it, and paste it here.

Bash command:
Confidence
88% confidence
Finding
The workflow explicitly asks the user to create a token with write scope and paste it into the session, then later persists it. This is a session-persistence and credential-handling risk because it encourages collecting a privileged secret into agent-visible context and potentially retaining it beyond the immediate operation.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The instruction to modify ~/.bashrc or ~/.zshrc to store an API key is given without any warning about the security and system-modification implications. Users may unknowingly authorize persistent credential storage and shell configuration changes that outlive the immediate task.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This code reads the entire diff file and sends it in a JSON request body to an external API endpoint. Although the script usage documents the API key and URL, it does not provide a clear warning that local code changes may be uploaded off-host, which is a privacy- and data-handling-relevant operation.

External Transmission

Medium
Category
Data Exfiltration
Content
'({diff:$diff, repository:$repo, base_commit:$base}
        + (if $head != "" then {head_commit_sha:$head} else {} end)
        + (if $branch != "" then {branch:$branch} else {} end))' \
      | curl -sS -o "$BODY_FILE" -w "%{http_code}" \
        --config "$CURL_CONFIG_FILE" \
        --data-binary @- \
        "$API_URL/v1/reviews"
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
BODY_FILE="$(mktemp)"
CURL_CONFIG_FILE="$(mktemp)"
chmod 600 "$CURL_CONFIG_FILE"
trap 'rm -f "$BODY_FILE" "$CURL_CONFIG_FILE"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$PROPEL_API_KEY" >"$CURL_CONFIG_FILE"
printf 'header = "Content-Type: application/json"\n' >>"$CURL_CONFIG_FILE"
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
BODY_FILE="$(mktemp)"
CURL_CONFIG_FILE="$(mktemp)"
chmod 600 "$CURL_CONFIG_FILE"
trap 'rm -f "$BODY_FILE" "$CURL_CONFIG_FILE"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$PROPEL_API_KEY" >"$CURL_CONFIG_FILE"
printf 'header = "Content-Type: application/json"\n' >>"$CURL_CONFIG_FILE"
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
BODY_FILE="$(mktemp)"
CURL_CONFIG_FILE="$(mktemp)"
chmod 600 "$CURL_CONFIG_FILE"
trap 'rm -f "$BODY_FILE" "$CURL_CONFIG_FILE"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$PROPEL_API_KEY" >"$CURL_CONFIG_FILE"
printf 'header = "Content-Type: application/json"\n' >>"$CURL_CONFIG_FILE"
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
printf 'header = "Content-Type: application/json"\n' >>"$CURL_CONFIG_FILE"

if ! HTTP_CODE="$(
  curl -sS -o "$BODY_FILE" -w "%{http_code}" \
    --config "$CURL_CONFIG_FILE" \
    -X POST "$API_URL/v1/reviews/$REVIEW_ID/comments/feedback" \
    --data-binary "$PAYLOAD"
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if ! code="$(
    jq -n --rawfile diff "$DIFF_FILE" --arg repo "$repo" --arg base "$BASE_COMMIT" \
      '{diff:$diff, repository:$repo, base_commit:$base}' \
      | curl -sS -o "$body_file" -w "%{http_code}" \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json" \
        --data-binary @- \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.