Back to skill

Security audit

String Search - Evomap Asset

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed string-search benchmark, but its shipped C code is unsafe for the general text-processing use cases it advertises.

Review this before installing or reusing it in production. It does not show hidden network, credential, persistence, or privilege behavior, but the C implementation should be treated as unsafe sample code until it validates input characters, checks sizes and allocations, documents supported encodings, and exposes an API that matches the README usage.

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
code.c:27
Finding
Out-of-Bounds Frequency-Table Access for Non-Lowercase Input<![CDATA[ ## Vulnerability Details **File Location**: `code.c:27`, `code.c:43-46`, and `code.c:99` **Vulnerability Type**: Unchecked array indexing using attacker-controlled character values **Risk Level**: High ### Vulnerable Code ```c double calculate_redundancy(const char *text, int n) { int freq[ALPHABET_SIZE] = {0}; for (int i = 0; i < n && i < 10000; i++) { freq[text[i] - 'a']++; } double entropy = 0; for (int i = 0; i < ALPHABET_SIZE; i++) { if (freq[i] > 0) { double p = (double)freq[i] / 10000; entropy -= p * log2(p); } } double max_entropy = log2(ALPHABET_SIZE); return 1.0 - (entropy / max_entropy); } int find_rarest_char_pos(const char *pattern, int m, const int *text_freq) { int rarest_pos = 0; int min_freq = text_freq[pattern[0] - 'a']; for (int i = 1; i < m; i++) { int freq = text_freq[pattern[i] - 'a']; if (freq < min_freq) { min_freq = freq; rarest_pos = i; } } return rarest_pos; } ``` The same unsafe indexing occurs while sampling text in the adaptive search: ```c int text_freq[ALPHABET_SIZE] = {0}; for (int i = 0; i < sample_size; i++) { text_freq[text[i] - 'a']++; } ``` ### Technical Analysis `ALPHABET_SIZE` is 26, but text and pattern bytes are converted directly into indices by subtracting `'a'`. The code does not establish that each byte falls within the inclusive range `'a'` through `'z'`. For example, an uppercase `A`, punctuation, a null byte, or a byte whose `char` representation is negative can produce an index below zero or above 25. The resulting operations access memory outside the `freq` or `text_freq` arrays. The writes in `calculate_redundancy()` and `yijing_v3_search()` can corrupt stack memory, while the accesses in `find_rarest_char_pos()` can read outside the frequency table. The documentation describes general text processing and log analysis, so callers could rea ...[truncated 1091 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a frequency table covering every possible byte and convert each input byte to `unsigned char` before indexing: ```c #define BYTE_VALUES 256 size_t freq[BYTE_VALUES] = {0}; unsigned char value = (unsigned char)text[i]; freq[value]++; ``` Apply the same design consistently to text and pattern indexing. If the algorithm intentionally supports only lowercase ASCII, validate every byte first and return a documented error for unsupported input. Additional hardening should include: 1. Replace signed `int` lengths with `size_t` where practical. 2. Validate that pointers are non-null when their corresponding lengths are nonzero. 3. Clearly document the accepted character encoding and alphabet. 4. Add tests containing uppercase characters, punctuation, null bytes, bytes above `0x7f`, and platforms where `char` is signed. 5. Compile and test with AddressSanitizer and UndefinedBehaviorSanitizer. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
code.c:60
Finding
Empty Pattern Causes Invalid Memory Access in KMP Search<![CDATA[ ## Vulnerability Details **File Location**: `code.c:60-74` **Vulnerability Type**: Missing empty-pattern and allocation validation **Risk Level**: High ### Vulnerable Code ```c void compute_lps(int *lps, const char *pattern, int m) { int len = 0; lps[0] = 0; int i = 1; while (i < m) { if (pattern[i] == pattern[len]) { len++; lps[i] = len; i++; } else { if (len != 0) len = lps[len - 1]; else { lps[i] = 0; i++; } } } } int kmp_search(const char *text, int n, const char *pattern, int m) { int *lps = malloc(m * sizeof(int)); compute_lps(lps, pattern, m); int count = 0, i = 0, j = 0; while (i < n) { if (pattern[j] == text[i]) { i++; j++; } if (j == m) { count++; j = lps[j - 1]; } else if (i < n && pattern[j] != text[i]) { if (j != 0) j = lps[j - 1]; else i++; } } free(lps); return count; } ``` ### Technical Analysis `kmp_search()` does not require `m` to be positive and does not check whether `malloc()` succeeds. If `m` is zero, `malloc(0)` may return either a null pointer or a non-dereferenceable unique pointer. `compute_lps()` then unconditionally writes to `lps[0]`. The search loop also reads `pattern[0]` even when the pattern length is zero. If `m` is negative, conversion to `size_t` during allocation can request an unexpectedly large allocation; a failed allocation is then passed to `compute_lps()` and dereferenced. Even with a valid positive length, memory exhaustion can make `malloc()` return null, which is not handled. ### Attack Path 1. An untrusted caller invokes `kmp_search()` with `m == 0`, a negative length, or an extremely large pattern length. 2. The function performs a zero-sized, oversized, or failed allocation. 3. The returned pointer is passed to `compute_lps()` without validation. 4. `compute_lps()` writes to `lps[0]`, or the search loop reads ...[truncated 730 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Define and enforce explicit behavior for empty patterns before allocating memory: ```c int kmp_search(const char *text, size_t n, const char *pattern, size_t m) { if (text == NULL || pattern == NULL) return -1; if (m == 0) return 0; /* Or another documented empty-pattern result. */ if (m > SIZE_MAX / sizeof(int)) return -1; int *lps = malloc(m * sizeof(*lps)); if (lps == NULL) return -1; /* Continue only after validation. */ } ``` Also: 1. Make `compute_lps()` reject null pointers and zero lengths, or keep it private and call it only after validated preconditions. 2. Use `size_t` for nonnegative buffer dimensions. 3. Distinguish errors from valid match counts through a documented return convention or an output parameter. 4. Test zero-length, negative-origin, maximum-size, null-pointer, and allocation-failure cases. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
code.c:280
Finding
Unchecked CLI Dimensions Enable Arithmetic Faults, Invalid Copies, and Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `code.c:137-154`, `code.c:188-203`, and `code.c:280-290` **Vulnerability Type**: Unvalidated numeric input and unchecked memory allocation **Risk Level**: High ### Vulnerable Code The allocation and pattern-generation functions trust signed dimensions: ```c char* generate_random_text(int size) { char *text = malloc(size); for (int i = 0; i < size; i++) text[i] = 'a' + (rand() % ALPHABET_SIZE); return text; } char* generate_high_redundancy_text(int size) { char *text = malloc(size); for (int i = 0; i < size; i++) text[i] = 'a' + (rand() % 5); return text; } char* generate_pattern(const char *text, int text_size, int pattern_size) { char *pattern = malloc(pattern_size + 1); int start = rand() % (text_size - pattern_size); strncpy(pattern, text + start, pattern_size); pattern[pattern_size] = '\0'; return pattern; } ``` Benchmark dimensions are also used directly in allocation arithmetic: ```c double **results = malloc(num_methods * sizeof(double*)); for (int i = 0; i < num_methods; i++) results[i] = malloc(repetitions * sizeof(double)); int total = num_methods * repetitions; int *seq = malloc(total * sizeof(int)); ``` The values originate from unchecked command-line parsing: ```c for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--size") == 0 && i + 1 < argc) text_size = atoi(argv[++i]); else if (strcmp(argv[i], "--pattern") == 0 && i + 1 < argc) pattern_size = atoi(argv[++i]); else if (strcmp(argv[i], "--redundancy") == 0 && i + 1 < argc) high_redundancy = (atoi(argv[++i]) > 0); else if (strcmp(argv[i], "--repetitions") == 0 && i + 1 < argc) repetitions = atoi(argv[++i]); } ``` ### Technical Analysis `atoi()` provides no reliable error reporting and accepts negative or extreme values without range validation. The program does not establish the required relationships among `text_size`, ...[truncated 1841 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Replace `atoi()` with checked conversion using `strtol()` or `strtoull()`. Verify that: 1. The complete input string was consumed. 2. No conversion overflow or underflow occurred. 3. `text_size` is positive and below a documented maximum. 4. `pattern_size` is positive and strictly less than or equal to `text_size`; adjust `generate_pattern()` if equality should be valid. 5. `repetitions` is positive and capped at a reasonable maximum. 6. Every multiplication and addition used for allocation is checked against `SIZE_MAX`. 7. Every allocation is checked before dereference. 8. Partially allocated resources are freed on failure. A safe start-position calculation should explicitly handle equal dimensions: ```c if (pattern_size == 0 || pattern_size > text_size) return NULL; size_t range = text_size - pattern_size; size_t start = (range == 0) ? 0 : (size_t)rand() % (range + 1); ``` Use `memcpy()` with previously validated lengths instead of relying on `strncpy()` for binary-sized regions. Introduce global workload limits so argument-controlled text sizes and repetition counts cannot exhaust host memory or CPU. ]]>
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • 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 (5)

Vague Triggers

Medium
Confidence
93% confidence
Finding
The Capsule trigger list is broad and generic, including phrases like 'String search needed' and 'Pattern matching in large text' in two languages without additional activation constraints. This increases the chance of unintended invocation in unrelated workflows, which could cause an agent to apply optimization changes when not explicitly requested, expanding operational risk even though the skill itself appears performance-focused rather than overtly malicious.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The file consistently presents the skill name, description, features, and use cases in both English and Chinese, indicating a language/locale behavior embedded in the skill documentation. Under the policy rule, language constraints or defaults should be user-selectable or clearly justified; no such opt-in or justification is provided here.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
This C file contains natural-language comments and user-facing console output in Chinese, including the title and status messages. Because the skill does not offer any language selection or indicate that it is intentionally limited to a Chinese-speaking context, it imposes a locale choice without user opt-in.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The code and surrounding comments describe the custom search routine as "易经优化 v3" and define the function as yijing_v3_search, but the benchmark labels that same function as "yijing_v2" in the names array. This is an active documentation/output contradiction that can mislead users about which algorithm version was actually tested and saved in results.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
The manifest includes both English and Chinese strings in matching signals, triggers, and summaries, but it does not state whether language selection is user-driven or whether the skill is intended for a specific bilingual context. This can create an implicit locale behavior without explicit opt-in or justification.

Static analysis

No suspicious patterns detected.