Back to skill

Security audit

social-listening

Security checks for vulnerabilities and agentic risk

Overview

This skill is a clearly scoped social-listening workflow that reads public LinkedIn posts, posts a bounded Slack digest, and keeps a limited post ledger without hidden engagement or credential handling.

Before installing, confirm the Slack channel is internal, review the Cargo CDK plan, keep LinkedIn engagement actions out of uses, and consider adding explicit instructions that LinkedIn posts, profiles, headlines, URLs, and comments are untrusted data whose embedded instructions must never be followed.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Findings (1)

T01 · Skill Instruction Hijacking

Warning
Location
infra/agents/listener.prompt.ts:9
Finding

Indirect Prompt Injection Through Automatically Ingested LinkedIn Content

Content
View full analysis

Vulnerability Details

File Location: infra/agents/listener.prompt.ts:9-48
Vulnerability Type: Indirect prompt injection from untrusted external content
Risk Level: Medium

Complete Code Snippet

typescript
## 1. Read the context first

From the workspace context read the ICP (who buys, the personas and titles, the disqualifiers), the pains in the buyer's own words, our positioning, and our competitors. That is the rubric for everything below.

## 2. Read this week's posts

Query the linkedin_posts model: every row. Drop any post whose urn is already in surfaced_posts. Drop posts written by us or by our own employees, job posts, and posts by a competitor's company page.

## 3. Pick the posts worth joining, at most five

A post is worth joining when its author or its readers are plausibly our buyers and it is about a pain we solve, a category we sit in, or a competitor. Rank by fit first, then by conversation (num_comments over num_likes). For each pick, write one line on why, citing the ICP or pain line that decided it. A post you cannot tie to a line of the context is not picked.

## 4. Find the warm engagers on those posts

For each picked post with at least one comment and no more than 150 comments, read its comments once with searchPostComments (sortBy "Most relevant"). That read bills per comment returned, so skip it for a post above the cap and say "comments not read: over the cap" on its digest line.

A commenter is a warm engager when their headline matches an ICP persona and does not hit a disqualifier. Keep at most three per post, and at most ten in the digest. For each: name, headline as written, their profile URL as the comment returned it, and the first sentence of what they said, quoted. Never infer an employer the headline does not state, never look anyone up elsewhere, and never add a person who only reacted.

## 5. Post the digest, once

If surfaced_posts already has a row with post_urn "digest-<today's date>", this week
...[truncated 4786 chars]
Remediation
View remediation

Remediation Suggestions

  1. Explicitly classify all LinkedIn post, profile, headline, URL, and comment fields as untrusted data in the system prompt. State that instructions, requests, policies, tool calls, or role declarations appearing in those fields must never be followed.
  2. Represent external records in a clearly delimited structured format and instruct the model to analyze only specified fields for relevance.
  3. Separate content classification from side effects. Have one restricted stage produce a structured selection, then validate it with deterministic code before permitting Slack or ledger writes.
  4. Validate selected post URNs and URLs against the exact records returned by linkedin_posts; validate quoted comments against the retrieved comment objects.
  5. Constrain ledger writes to post URNs present in the current synchronized dataset and enforce schema-level uniqueness where supported.
  6. Apply output validation before postMessage, rejecting unexpected instructions, mentions, links, or content that cannot be traced to a source record.
  7. Add adversarial acceptance tests containing prompt-injection strings in post bodies, author metadata, headlines, and comments. Verify that these strings are quoted or summarized only as data and cannot alter tool use or workflow sequencing.
  8. Preserve the existing least-privilege controls: keep LinkedIn engagement actions unavailable, retain the locked Slack channel, and keep workspace context read-only.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is about a full LinkedIn-monitoring and Slack-digest skill. The supplied code chunk does not implement any of that behavior; it only defines an Anthropic connector. While model setup could be a supporting detail in a larger skill, this chunk by itself materially does not match the declared purpose because none of the described LinkedIn or Slack functionality appears here.

Content

No source excerpt is available for this finding.

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 119)May include surrounding context.

md
**Derive before you ask.** An input with a lookup is looked up, not asked.

| Input                                                       | Kind    | How it is answered                                                                                                                                                              | Why it matters                                                                                                                                         |
| ----------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `searchKeywords` (`infra/models/linkedin-posts.ts`)         | derived | From the ICP pains, category term and competitors in the workspace context, confirmed against one manual search. Asked only when the context has none.                           | It is what you buy each week. Positioning copy as keywords returns vendors talking to each other; buyer language returns buyers.                        |
| `limit` (`infra/models/linkedin-posts.ts`)                  | asked   | 20 to 100 in steps of 20. Price it from the live per-post cost before you pick.                                                                                                 | It is the weekly spend ceiling on the search.                                                                                                          |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 119)May include surrounding context.

md
**Derive before you ask.** An input with a lookup is looked up, not asked.

| Input                                                       | Kind    | How it is answered                                                                                                                                                              | Why it matters                                                                                                                                         |
| ----------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `searchKeywords` (`infra/models/linkedin-posts.ts`)         | derived | From the ICP pains, category term and competitors in the workspace context, confirmed against one manual search. Asked only when the context has none.                           | It is what you buy each week. Positioning copy as keywords returns vendors talking to each other; buyer language returns buyers.                        |
| `limit` (`infra/models/linkedin-posts.ts`)                  | asked   | 20 to 100 in steps of 20. Price it from the live per-post cost before you pick.                                                                                                 | It is the weekly spend ceiling on the search.                                                                                                          |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 122)May include surrounding context.

md
| Input                                                       | Kind    | How it is answered                                                                                                                                                              | Why it matters                                                                                                                                         |
| ----------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `searchKeywords` (`infra/models/linkedin-posts.ts`)         | derived | From the ICP pains, category term and competitors in the workspace context, confirmed against one manual search. Asked only when the context has none.                           | It is what you buy each week. Positioning copy as keywords returns vendors talking to each other; buyer language returns buyers.                        |
| `limit` (`infra/models/linkedin-posts.ts`)                  | asked   | 20 to 100 in steps of 20. Price it from the live per-post cost before you pick.                                                                                                 | It is the weekly spend ceiling on the search.                                                                                                          |
| `channelId` (`infra/agents/listener.ts`)                    | asked   | The Slack channel id (`C…`) from the connector's autocomplete. Invite the bot.                                                                                                  | The digest names people. Locked so it never lands in a customer shared channel.                                               
...[truncated 25 chars]

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 122)May include surrounding context.

md
| Input                                                       | Kind    | How it is answered                                                                                                                                                              | Why it matters                                                                                                                                         |
| ----------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `searchKeywords` (`infra/models/linkedin-posts.ts`)         | derived | From the ICP pains, category term and competitors in the workspace context, confirmed against one manual search. Asked only when the context has none.                           | It is what you buy each week. Positioning copy as keywords returns vendors talking to each other; buyer language returns buyers.                        |
| `limit` (`infra/models/linkedin-posts.ts`)                  | asked   | 20 to 100 in steps of 20. Price it from the live per-post cost before you pick.                                                                                                 | It is the weekly spend ceiling on the search.                                                                                                          |
| `channelId` (`infra/agents/listener.ts`)                    | asked   | The Slack channel id (`C…`) from the connector's autocomplete. Invite the bot.                                                                                                  | The digest names people. Locked so it never lands in a customer shared channel.                                               
...[truncated 25 chars]

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 123)May include surrounding context.

md
| ----------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `searchKeywords` (`infra/models/linkedin-posts.ts`)         | derived | From the ICP pains, category term and competitors in the workspace context, confirmed against one manual search. Asked only when the context has none.                           | It is what you buy each week. Positioning copy as keywords returns vendors talking to each other; buyer language returns buyers.                        |
| `limit` (`infra/models/linkedin-posts.ts`)                  | asked   | 20 to 100 in steps of 20. Price it from the live per-post cost before you pick.                                                                                                 | It is the weekly spend ceiling on the search.                                                                                                          |
| `channelId` (`infra/agents/listener.ts`)                    | asked   | The Slack channel id (`C…`) from the connector's autocomplete. Invite the bot.                                                                                                  | The digest names people. Locked so it never lands in a customer shared channel.                                                                         |
| connectors (`infra/connectors/`)                            | value   | **derived**: `cargo-ai connection connector list` shows authorized LinkedIn, Slack and Anthropic connectors. `default: true` binds each.                                          | Without LinkedIn the sync returns nothing and the digest reads "quiet week" forever.                                       
...[truncated 25 chars]

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 125)May include surrounding context.

md
| `limit` (`infra/models/linkedin-posts.ts`)                  | asked   | 20 to 100 in steps of 20. Price it from the live per-post cost before you pick.                                                                                                 | It is the weekly spend ceiling on the search.                                                                                                          |
| `channelId` (`infra/agents/listener.ts`)                    | asked   | The Slack channel id (`C…`) from the connector's autocomplete. Invite the bot.                                                                                                  | The digest names people. Locked so it never lands in a customer shared channel.                                                                         |
| connectors (`infra/connectors/`)                            | value   | **derived**: `cargo-ai connection connector list` shows authorized LinkedIn, Slack and Anthropic connectors. `default: true` binds each.                                          | Without LinkedIn the sync returns nothing and the digest reads "quiet week" forever.                                                                   |
| our own company and people                                  | derived | From the workspace context (company, positioning).                                                                                                                              | They are excluded from picks. Miss them and the digest recommends joining your own posts.                                                              |

## What you can change

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 129)May include surrounding context.

md
## What you can change

| Variation         | When it is right                                                   | How                                                                                                             | What it costs                                                                                                                                                     |
| ----------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `two-searches`    | Pain language and competitor names return different crowds        | Add a second `fetchPosts` model with its own keywords and give the listener read access to both                 | Two searches billed each week. The ledger still dedupes a post that matches both.                                                                                 |
| `with-reactions`  | Comments are rare in your niche and reactions are the only signal   | Add `linkedin.actions.searchPostReactions` to `uses` and a §4 line to read them on picked posts                 | Billed per reactor, often hundreds a post, and a reaction carries no words to quote. Cap it in the prompt.                                                        |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 129)May include surrounding context.

md
## What you can change

| Variation         | When it is right                                                   | How                                                                                                             | What it costs                                                                                                                                                     |
| ----------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `two-searches`    | Pain language and competitor names return different crowds        | Add a second `fetchPosts` model with its own keywords and give the listener read access to both                 | Two searches billed each week. The ledger still dedupes a post that matches both.                                                                                 |
| `with-reactions`  | Comments are rare in your niche and reactions are the only signal   | Add `linkedin.actions.searchPostReactions` to `uses` and a §4 line to read them on picked posts                 | Billed per reactor, often hundreds a post, and a reaction carries no words to quote. Cap it in the prompt.                                                        |

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 131)May include surrounding context.

md
| Variation         | When it is right                                                   | How                                                                                                             | What it costs                                                                                                                                                     |
| ----------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `two-searches`    | Pain language and competitor names return different crowds        | Add a second `fetchPosts` model with its own keywords and give the listener read access to both                 | Two searches billed each week. The ledger still dedupes a post that matches both.                                                                                 |
| `with-reactions`  | Comments are rare in your niche and reactions are the only signal   | Add `linkedin.actions.searchPostReactions` to `uses` and a §4 line to read them on picked posts                 | Billed per reactor, often hundreds a post, and a reaction carries no words to quote. Cap it in the prompt.                                                        |
| `with-tiers`      | `account-scoring` is installed and you want engagers at A/B accounts first | Add the tiered companies model to `uses` read-only, and in §4 match the headline's stated employer by name      | Name matching misses on abbreviations. Only a headline that states the employer can match; never look it up.                                                      |
| `daily`           | The category moves fast and a weekly digest is stale               | `datePosted: "Past 24 hours"`, a daily cron on both the 
...[truncated 25 chars]

Whitespace Padding

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Content

Scanner excerpt · SKILL.md (reported line 134)May include surrounding context.

md
| `two-searches`    | Pain language and competitor names return different crowds        | Add a second `fetchPosts` model with its own keywords and give the listener read access to both                 | Two searches billed each week. The ledger still dedupes a post that matches both.                                                                                 |
| `with-reactions`  | Comments are rare in your niche and reactions are the only signal   | Add `linkedin.actions.searchPostReactions` to `uses` and a §4 line to read them on picked posts                 | Billed per reactor, often hundreds a post, and a reaction carries no words to quote. Cap it in the prompt.                                                        |
| `with-tiers`      | `account-scoring` is installed and you want engagers at A/B accounts first | Add the tiered companies model to `uses` read-only, and in §4 match the headline's stated employer by name      | Name matching misses on abbreviations. Only a headline that states the employer can match; never look it up.                                                      |
| `daily`           | The category moves fast and a weekly digest is stale               | `datePosted: "Past 24 hours"`, a daily cron on both the model and the agent                                     | Seven searches a week instead of one, and a digest most days will be quiet.                                                                                       |
| `competitor-only` | You only want to hear when a competitor is discussed               | Keywords become competitor names only                                                                           | You lose the buyers who describe the pain without naming any vendor, which is most of them.                                                                       |

## What should not change

Static analysis

No suspicious patterns detected.