Back to skill

Security audit

call-capture

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent and disclosed, but it needs review because a scheduled coding agent reads untrusted call transcripts while holding recorder credentials, workspace environment access, and repository pull-request authority.

Install only after reviewing the generated Cargo plan and limiting the workspace environment available to the agent. Prefer a restricted GitHub token, add explicit prompt rules that transcript content is data only, add secret scanning before any push or PR body creation, and verify the selected recorder adapter with a dry run before deploying the daily cron.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/collect/recorders/turns.ts:24
Finding

Untrusted call transcripts are processed by a credential-bearing repository-write agent

Content
View full analysis

Vulnerability Details

File Location: scripts/collect/recorders/turns.ts:24-38; scripts/collect/recorder.ts:362-389; infra/agents/call-scribe.prompt.ts:31-36,64-105; infra/agents/call-scribe.ts:27-31,43-52
Vulnerability Type: Indirect prompt injection across an external-content trust boundary
Risk Level: High

Vulnerable Code

scripts/collect/recorders/turns.ts:24-38 preserves recorder-supplied speaker names and transcript text as Markdown without identifying or isolating them as untrusted instructions:

ts
export function renderTurns(turns: Turn[]): string | null {
  const merged: { speaker: string; parts: string[] }[] = [];

  for (const turn of turns) {
    const text = turn.text.trim();
    if (text === "") continue;
    const speaker =
      turn.speaker !== undefined && turn.speaker.trim() !== ""
        ? turn.speaker.trim()
        : "Speaker";

    const last = merged[merged.length - 1];
    if (last !== undefined && last.speaker === speaker) last.parts.push(text);
    else merged.push({ speaker, parts: [text] });
  }

  if (merged.length === 0) return null;
  return merged
    .map((turn) => `**${turn.speaker}:** ${turn.parts.join(" ")}`)
    .join("\n\n");
}

scripts/collect/recorder.ts:362-389 writes that external content directly into a raw Markdown file:

ts
  const frontmatter = [
    "---",
    `title: "RAW: ${call.subject.replace(/"/g, "'")}"`,
    `date: ${day}`,
    `attendees: [${attendees}]`,
    // The written half of the idempotency key. Its reader is capturedIds(),
    // and both are built from `provider` so they cannot disagree.
    `source: ${provider} ${call.id}`,
    "status: raw, awaiting scribe",
    "---",
    "",
  ].join("\n");

  writeFileSync(path, `${frontmatter}\n${body}\n`);
  return relative(ROOT, path);
}

infra/agents/call-scribe.prompt.ts:31-36,64-105 instructs the scheduled agent to read repository instructions and process each raw capture, but it does not establish that tr ...[truncated 5792 chars]

Remediation
View remediation

Remediation Suggestions

  1. Explicitly classify recorder content as untrusted data.

    • Add a high-priority instruction stating that transcript text, speaker names, subjects, notes, and embedded Markdown are data only.
    • State that commands, tool requests, repository instructions, links, or requests for secrets appearing in those fields must never be followed.
  2. Separate collection credentials from the LLM process.

    • Run the deterministic collector in an isolated process with access only to CALL_RECORDER_API_KEY.
    • Remove recorder credentials and unrelated workspace variables from the environment before starting the Claude Code harness.
    • Use an explicit environment-variable allowlist rather than inheriting the entire workspace catalog.
  3. Constrain the agent’s filesystem and Git permissions.

    • Permit writes only under the expected raw, call-log, and context directories.
    • Deny changes to infrastructure, agent prompts, executable scripts, configuration files, and CI definitions through technical policy rather than prompt text alone.
    • Use a token restricted to creating branches and pull requests only for the bound repository.
  4. Restrict runtime capabilities.

    • Disable arbitrary outbound network access during transcript processing.
    • Prevent shell commands from reading environment variables or unrelated files.
    • Replace the general coding harness with a constrained transformation worker where feasible.
  5. Use structured input and output boundaries.

    • Pass transcripts as typed data fields or escaped blocks clearly delimited from instructions.
    • Require generated output to conform to a schema containing only the approved log and context fields.
    • Treat Markdown control syntax in speaker names, subjects, and transcript text as literal content.
  6. Add a mandatory pre-push security gate.

    • Scan staged changes and pull-request text for workspace secrets, credential patterns, unexpected files, ex ...[truncated 577 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (111)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a complete workflow for collecting recorded calls from multiple sources, converting them into knowledge-base updates, and creating a PR in a go-to-market repository. The supplied code chunk does none of that. It only configures an Anthropic connector as the default model backend for a harness agent. While an LLM connector could be a supporting component of such a system, this chunk's actual behavior is purely runtime/model integration and lacks any recorder access, scheduling, transcript handling, cadence/context logic, GitHub operations, or adapter implementation. Therefore the code chunk does not accurately represent the declared skill behavior and should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
`scripts/collect/recorder.ts` — a `provider` slug plus `listReady(from, to)`, `transcript(id)`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
`scripts/collect/recorder.ts` — a `provider` slug plus `listReady(from, to)`, `transcript(id)`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
`scripts/collect/recorder.ts` — a `provider` slug plus `listReady(from, to)`, `transcript(id)`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
`scripts/collect/recorder.ts` — a `provider` slug plus `listReady(from, to)`, `transcript(id)`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
`scripts/collect/recorder.ts` — a `provider` slug plus `listReady(from, to)`, `transcript(id)`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
joining, `fireflies.ts` for GraphQL, `granola.ts` for a list too thin to carry attendees.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| adapter state (`scripts/collect/recorders/index.ts`) | value | **derived**: `--list` prints which adapters were verified against a live workspace (Avoma) and

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| adapter state (`scripts/collect/recorders/index.ts`) | value | **derived**: `--list` prints which adapters were verified against a live workspace (Avoma) and

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| adapter state (`scripts/collect/recorders/index.ts`) | value | **derived**: `--list` prints which adapters were verified against a live workspace (Avoma) and

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
doubling in size. `resolve()` refuses the mismatch and `evals/contract.mjs` fails on it; leave

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 35)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 309)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 313)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 699)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 766)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 812)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · evals/contract.mjs (reported line 857)May include surrounding context.

js
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
70% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/collect/recorder.ts (reported line 92)May include surrounding context.

ts
// Read at module load in recorder.ts, so it has to be set before the import.
process.env["CALL_CAPTURE_PACE_MS"] = "0";
process.env["CALL_RECORDER_API_KEY"] = "test-key";

const { RECORDER } = await import("../scripts/collect/config.ts");
const { backoffSeconds, checkFlags, ConfigError, pageGuard, recorderKeyPair } =

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/providers.md (reported line 222)May include surrounding context.

md
`workspaceEnv("CALL_RECORDER_API_KEY")` is the fourth spelling and `@cargo-ai/cdk` rejects it in a harness
`env` block, by type: a pointer there could only restate a variable the sandbox shell already reads.
It is for credential fields on resources that inherit nothing — a connector's access token, where
`workspaceEnv()` buys exactly what the catalog buys here.

The name is deliberately not the vendor's, so a swap changes one catalog entry and one slug and

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/recorder-apis.md (reported line 449)May include surrounding context.

md
`workspaceEnv("CALL_RECORDER_API_KEY")` is the fourth spelling and `@cargo-ai/cdk` rejects it in a harness
`env` block, by type: a pointer there could only restate a variable the sandbox shell already reads.
It is for credential fields on resources that inherit nothing — a connector's access token, where
`workspaceEnv()` buys exactly what the catalog buys here.

The name is deliberately not the vendor's, so a swap changes one catalog entry and one slug and

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/recorder-apis.md (reported line 438)May include surrounding context.

md
## Grain

**Public API.** Yes, with two token types on different plans. **Personal Access Tokens** are
available on Starter, Business and Enterprise and carry the generating user's own permissions.
**Workspace Access Tokens** read *all* workspace data, require admin access, and are **Business
and Enterprise only**. The Free plan has no API access.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/recorder-apis.md (reported line 440)May include surrounding context.

md
## Grain

**Public API.** Yes, with two token types on different plans. **Personal Access Tokens** are
available on Starter, Business and Enterprise and carry the generating user's own permissions.
**Workspace Access Tokens** read *all* workspace data, require admin access, and are **Business
and Enterprise only**. The Free plan has no API access.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/recorder-apis.md (reported line 570)May include surrounding context.

md
## Read.ai — not shipped

**Why not.** The credential cannot survive an unattended cron. There are no static API keys: it is
OAuth 2.1 with dynamic client registration, access tokens that expire after **ten minutes**, and
single-use refresh tokens that rotate on every exchange. A daily job would have to persist the
rotated refresh token somewhere durable and a broken chain needs a human with a browser — which is
a different shape of credential from one static `CALL_RECORDER_API_KEY` in the workspace catalog,

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/recorder-apis.md (reported line 596)May include surrounding context.

md
## Read.ai — not shipped

**Why not.** The credential cannot survive an unattended cron. There are no static API keys: it is
OAuth 2.1 with dynamic client registration, access tokens that expire after **ten minutes**, and
single-use refresh tokens that rotate on every exchange. A daily job would have to persist the
rotated refresh token somewhere durable and a broken chain needs a human with a browser — which is
a different shape of credential from one static `CALL_RECORDER_API_KEY` in the workspace catalog,

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/collect/recorder.ts:92