Back to skill

Security audit

Belief State Runtime

Security checks for vulnerabilities and agentic risk

Overview

The skill is a plausible fact-checking helper, but it automatically pushes user claims into search/LLM workflows without clear consent and has integrity weaknesses in how it handles untrusted evidence.

Review before installing. Do not use this skill on private, proprietary, personal, regulated, or unreleased claims unless you are comfortable with the host agent searching the web or sending claim/evidence text to its configured LLM. Treat its VERIFIED/CONTESTED/UNCERTAIN output as advisory because adversarial evidence text and spoofed-looking source domains can influence the score.

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

Warning
Location
assess.py:234
Finding

Prompt Injection Through Untrusted Claim and Evidence Content

Content
View full analysis

Vulnerability Details

File Location: assess.py:234-251
Vulnerability Type: Prompt injection caused by direct interpolation of untrusted content
Risk Level: Medium

Vulnerable Code

python
prompt = f"""You are a fact verification assistant. Based on the following [Claim] and [New Evidence], answer 6 judgments. Output only a JSON object, no other text.

Claim: {claim}
New Evidence: {evidence[:1500]}

Judgment criteria:
1. direct_support: Does the evidence directly support the claim?
2. new_info: Does the evidence provide new information not previously mentioned?
3. logical_consistent: Is the evidence logically consistent with previously known information?
4. direct_refute: Does the evidence explicitly refute the claim?
5. limitation: Does the evidence point out limitations or exceptions?
6. error_outdated: Does the evidence reveal that the claim contains errors or outdated info?

Output format:
{{"direct_support": true/false, "new_info": true/false, "logical_consistent": true/false, "direct_refute": true/false, "limitation": true/false, "error_outdated": false}}"""

The same unsafe prompt construction is duplicated in get_assessment_prompt() at assess.py:426-443.

Technical Analysis

The claim and evidence arguments are inserted directly into an instruction-bearing prompt. The prompt does not clearly isolate these values as untrusted data and does not instruct the model to ignore commands contained within them.

An attacker can therefore place model instructions in either field, such as directions to disregard the judgment criteria and return attacker-selected Boolean values. Those values directly control support_score, refute_score, and the resulting belief state. Truncating evidence to 1,500 characters limits input size but does not prevent injection.

This is classified as an insecure coding practice rather than malicious skill-level instruction hijacking because the flaw ...[truncated 1011 chars]

Remediation
View remediation

Remediation Suggestions

  • Put untrusted values in strongly delimited data blocks and explicitly state that instructions inside those blocks must never be followed.
  • Prefer structured model APIs with distinct trusted-instruction and untrusted-data fields where available.
  • Validate model output against a strict JSON schema requiring exactly the six expected Boolean properties.
  • Reject extra keys, prose, malformed values, and contradictory combinations.
  • Add adversarial prompt-injection tests for both claim and evidence.
  • Apply the same protections to both _extract_llm_features() and get_assessment_prompt().
  • Where possible, corroborate model judgments with deterministic checks or independent evidence processing rather than treating one model response as authoritative.

T09 · Insecure Skill Coding Practices

Warning
Location
assess.py:191
Finding

Spoofable Source Reliability Scoring Through Hostname Substring Matching

Content
View full analysis

Vulnerability Details

File Location: assess.py:191-217
Vulnerability Type: Improper validation of evidence-source hostnames
Risk Level: Medium

Vulnerable Code

python
def _domain_score(self, domain: str) -> float:
    domain = domain.lower()
    for pattern, score in [
        ("gov", 0.9),
        ("edu", 0.9),
        ("who.int", 0.9),
        ("pubmed.ncbi", 0.9),
        ("nature.com", 0.9),
        ("science.org", 0.9),
        ("reuters.com", 0.7),
        ("bbc.com", 0.7),
        ("apnews.com", 0.7),
        ("nytimes.com", 0.7),
        ("theguardian.com", 0.7),
        ("arxiv.org", 0.6),
        ("wikipedia.org", 0.5),
        ("twitter.com", 0.3),
        ("x.com", 0.3),
        ("reddit.com", 0.3)
    ]:
        if pattern in domain:
            return score
    if domain.endswith(".gov") or domain.endswith(".edu"):
        return 0.9
    if domain.endswith(".org"):
        return 0.6
    return 0.5

Technical Analysis

Trusted domain patterns are matched anywhere in the extracted hostname using if pattern in domain. This does not enforce an exact hostname or a valid subdomain boundary. As a result, attacker-controlled hostnames such as who.int.attacker.example, fake-reuters.com, or government-facts.example can match trusted patterns and receive an elevated source-reliability score.

The generic patterns gov and edu are especially broad and can match arbitrary hostname text. The resulting score contributes 40% of quality_factor, which in turn contributes 40% of raw confidence.

Attack Path

  1. The attacker controls or references a domain whose hostname contains a trusted substring.
  2. The attacker includes an HTTP or HTTPS URL for that hostname in the evidence.
  3. _extract_rule_signals() extracts the hostname and passes it to _domain_score().
  4. Substring matching assigns the hostname an unjustifiably high reliabil ...[truncated 485 chars]
Remediation
View remediation

Remediation Suggestions

  • Parse URLs with a standards-compliant URL parser and use only the normalized hostname.
  • Normalize case, trailing dots, Unicode IDNs, and punycode before comparison.
  • Replace substring checks with exact-host or label-boundary suffix checks:
    • host == trusted_domain
    • host.endswith("." + trusted_domain)
  • Represent government and educational sources as explicit suffix rules such as .gov and .edu, rather than matching gov or edu anywhere.
  • Use a maintained allowlist and define how subdomains inherit trust.
  • Add tests for deceptive domains, including who.int.attacker.example, fake-reuters.com, and Unicode lookalikes.
  • Consider verifying that referenced sources were actually retrieved and that the cited content supports the evidence.

T09 · Insecure Skill Coding Practices

Note
Location
assess.py:177
Finding

Future-Year Input Can Crash or Corrupt Temporal Freshness Scoring

Content
View full analysis

Vulnerability Details

File Location: assess.py:177-181
Vulnerability Type: Improper numeric validation resulting in division by zero or invalid scoring
Risk Level: Low

Vulnerable Code

python
years = re.findall(r'\b((?:19|20)\d{2})\b', evidence)
if years:
    latest = max(int(y) for y in years)
    age = 2026 - latest
    result.temporal_freshness = round(1.0 / (1.0 + age), 4)
else:
    result.temporal_freshness = 0.7

Technical Analysis

Temporal freshness is calculated relative to the hardcoded year 2026 without validating future dates. If evidence contains 2027, then age becomes -1, making the denominator zero and raising ZeroDivisionError. Years after 2027 produce negative freshness values, violating the apparent expectation that scoring signals remain between zero and one.

Because _extract_rule_signals() does not catch this exception, affected assessment operations can terminate before returning a result.

Attack Path

  1. An attacker submits evidence containing the year 2027.
  2. The regular expression extracts that year and selects it as the latest year.
  3. The calculation produces age = -1.
  4. 1.0 / (1.0 + age) attempts division by zero.
  5. The assessment raises an exception and fails to return a belief state.

Alternatively, a later year such as 2028 produces a negative freshness value that contaminates quality and confidence calculations.

Impact Assessment

The issue permits denial of service for individual assessments and can corrupt confidence calculations for future-year inputs. It grants no additional privileges and does not enable code execution, data access, or persistence. Scope depends on whether an attacker can control the evidence text submitted to the assessment API.

Remediation
View remediation

Remediation Suggestions

  • Derive the current year from a trusted runtime clock rather than hardcoding it.
  • Define an explicit policy for future-dated evidence.
  • Clamp age to a safe nonnegative range before division, for example:
python
from datetime import datetime, timezone

current_year = datetime.now(timezone.utc).year
age = max(0, current_year - latest)
result.temporal_freshness = round(1.0 / (1.0 + age), 4)
  • Clamp the final freshness value to the documented interval [0.0, 1.0].
  • Add unit tests for the current year, the next year, distant future years, old years, and evidence containing multiple dates.
  • Handle malformed or semantically implausible dates without terminating the entire assessment.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger guidance is broad enough that normal trust-evaluation questions like “is this true?” or “can I trust this?” may invoke the skill in many contexts without clear user intent. In this skill, unintended invocation is more dangerous because the default workflow then mandates evidence search, which can cause unnecessary external lookups of user-provided claims or sensitive context.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill instructs the agent to always run a mandatory evidence-search workflow first, but does not require any user-facing notice or consent before sending claim content to search tools. This creates a real privacy and data-handling risk because sensitive user input, internal facts, or proprietary material could be queried externally under the guise of routine verification.

Content

No source excerpt is available for this finding.

Ssd 1

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

User-controlled claim and evidence are interpolated directly into an instruction prompt for the model with no delimiting, quoting discipline, or explicit instruction to treat embedded text as untrusted content rather than commands. Because this skill's core purpose is to evaluate truthfulness of arbitrary text, attackers can place adversarial instructions inside the evidence to bias the six boolean outputs, causing incorrect belief states and downstream trust decisions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill sends the raw claim and up to 1500 characters of evidence to an injected LLM function without any explicit disclosure, consent gate, or data-classification control. In many agent deployments, claim/evidence may contain sensitive user, enterprise, or regulated data, so this creates a real confidentiality and compliance risk if the injected model is remote, logged, or untrusted.

Content

No source excerpt is available for this finding.

Ssd 1

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The manual prompt flow exposes the same prompt-injection weakness, and may be more dangerous because it encourages the agent itself to answer the helper prompt directly. If claim/evidence contain hostile text such as instructions to output specific booleans, the agent can be manipulated into returning attacker-chosen assessments, undermining the integrity of the belief-state engine.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The interface text explicitly instructs the AI agent to answer the assessment prompt in a fixed format and the surrounding module language is English-only, with no user opt-in or language selection. Under the stated policy, language constraints should either offer choice or be clearly justified as region- or compliance-specific.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

The registration metadata exposes the skill through English-only natural-language descriptions, with no indication that users may choose another language or that English is required for a justified operational reason. This can be a policy issue when a skill implicitly fixes a language without opt-in.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.