Back to skill

Security audit

pager-triage

Security checks for vulnerabilities and agentic risk

Overview

This PagerDuty triage skill is mostly purpose-aligned, but it under-discloses real credential exposure risks for a tool that can use high-impact PagerDuty access.

Review before installing. Use a read-only, team-scoped PagerDuty token unless you truly need acknowledge/resolve/note actions, avoid running the smoke test with production credentials, and require explicit human confirmation for every write action. Treat incident text and notes as untrusted because attackers or integrations may place instructions inside PagerDuty content.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
scripts/pager-triage.sh:77
Finding
PagerDuty API Token Exposed Through Process Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pager-triage.sh:77-99` **Vulnerability Type**: Credential exposure through command-line arguments **Risk Level**: Medium ### Vulnerable Code ```bash local -a curl_args=( -s -w '\n%{http_code}' --max-time "$CURL_TIMEOUT" -H "Authorization: Token token=${PAGERDUTY_API_KEY}" -H "Content-Type: application/json" -X "$method" ) # Add From header for write operations if [[ "$method" != "GET" && -n "${PAGERDUTY_EMAIL:-}" ]]; then curl_args+=(-H "From: ${PAGERDUTY_EMAIL}") fi if [[ -n "$data" ]]; then curl_args+=(-d "$data") fi local response http_code body local attempt=0 local max_attempts=2 while (( attempt < max_attempts )); do response=$(curl "${curl_args[@]}" "$url" 2>/dev/null) || { ``` ### Technical Analysis The PagerDuty API token is expanded into the `curl` argument array and passed as an HTTP header using `-H`. Although the token originates from an environment variable, its expanded value becomes part of the `curl` process argument vector. On systems where process arguments are visible to other local users, diagnostic agents, container administrators, or monitoring software, the complete authorization header may be recovered while the request is running. This contradicts the assertion in `SECURITY.md` that credentials are never passed through command-line arguments. The same issue applies to `PAGERDUTY_EMAIL` during write requests, although disclosure of the API token presents the greater security risk. ### Attack Path 1. A legitimate user or agent invokes a PagerDuty command such as `incidents` or `detail`. 2. The script expands `PAGERDUTY_API_KEY` into the `curl` command-line argument containing the authorization header. 3. During the request, an attacker with sufficient same-host process visibility inspects the `curl` process through facilities such as `/proc`, process monitoring, container administration, or telemetry collection. 4. The attacker extracts the complete ...[truncated 1057 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not place the authorization header directly in the `curl` argument vector. - Supply sensitive curl configuration through standard input or a protected file descriptor so the token is not present in process arguments. For example, generate a curl configuration stream with restrictive handling and invoke `curl --config -`. - Avoid temporary files where possible. If a temporary credential-bearing configuration is unavoidable, create it with mode `0600`, place it in a trusted directory, install an exit trap, and securely remove it immediately after use. - Ensure tracing is disabled around credential processing and document that the script must not be run with `bash -x`. - Prevent command and process telemetry systems from collecting sensitive header values. - Prefer read-only, team-scoped API tokens for normal triage. Use a separate narrowly scoped token or execution context for write operations. - Add an automated test that observes the child process arguments and fails if the API token or email appears. - Update `SECURITY.md` so its credential-handling claims accurately reflect the implementation. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/smoke.sh:196
Finding
Smoke Test Discloses Credential-Derived Data and PagerDuty User Email<![CDATA[ ## Vulnerability Details **File Location**: `scripts/smoke.sh:196-203` **Vulnerability Type**: Sensitive information exposure in terminal and CI logs **Risk Level**: Low ### Vulnerable Code ```bash if [[ -n "${PAGERDUTY_API_KEY:-}" ]]; then pass "PAGERDUTY_API_KEY is set ($(echo "${PAGERDUTY_API_KEY}" | head -c 4)****)" else echo " ℹ️ PAGERDUTY_API_KEY not set — skipping live API tests (this is fine for smoke tests)" fi if [[ -n "${PAGERDUTY_EMAIL:-}" ]]; then pass "PAGERDUTY_EMAIL is set (${PAGERDUTY_EMAIL})" else ``` ### Technical Analysis The smoke test prints the first four characters of `PAGERDUTY_API_KEY` and the complete value of `PAGERDUTY_EMAIL`. Smoke-test output is commonly retained in CI logs, support transcripts, terminal recordings, or centralized log systems. The token prefix is not sufficient by itself to authenticate, but it is unnecessary credential-derived information that can be used to correlate tokens, identify token formats, or support social-engineering attempts. The full email is personally identifiable and identifies the PagerDuty account used for write operations. This behavior also conflicts with documentation claiming that credential values are never displayed. ### Attack Path 1. An operator runs the documented `scripts/smoke.sh` command in a shell or CI job where real PagerDuty environment variables are present. 2. The script interpolates part of the API token and the complete PagerDuty email into its status output. 3. The terminal, CI platform, or log collector retains the output. 4. A user with access to those logs obtains the token prefix and PagerDuty user identity. 5. The information may be used for account correlation, targeted phishing, social engineering, or to identify which credential was active during a particular run. The script does not disclose the complete API token through this output, so direct PagerDuty authentication is not possible from this finding alone. ### Impact Assessment The d ...[truncated 394 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Report only whether each variable is set. Do not print any portion of the token or the complete email. - Replace the affected output with messages such as `PAGERDUTY_API_KEY is set` and `PAGERDUTY_EMAIL is set`. - Clear production credentials before running smoke tests that do not require network access. - Run integration tests requiring real credentials only in isolated jobs with restricted log access. - Add secret-scanning checks to CI output and test fixtures. - Document that users should use dedicated test accounts and narrowly scoped test tokens. - Review existing CI and support logs for exposed PagerDuty email addresses and token prefixes, and remove retained copies where feasible. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/pager-triage.sh:250
Finding
Untrusted PagerDuty Content Is Forwarded to the Agent Without an Enforced Trust Boundary<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pager-triage.sh:250-302` **Vulnerability Type**: Indirect prompt injection through attacker-controlled incident data **Risk Level**: Medium ### Vulnerable Code ```bash { tool: "pd_incident_detail", incident: { id: $incident.incident.id, incident_number: $incident.incident.incident_number, title: $incident.incident.title, status: $incident.incident.status, urgency: $incident.incident.urgency, service: { id: ($incident.incident.service.id // null), name: ($incident.incident.service.summary // null) }, created_at: $incident.incident.created_at, escalation_policy: { id: ($incident.incident.escalation_policy.id // null), name: ($incident.incident.escalation_policy.summary // null) }, assignments: [ ($incident.incident.assignments // [])[] | { name: .assignee.summary, email: (.assignee.html_url // null), escalation_level: (.escalation_level // 1) } ], acknowledgers: [ ($incident.incident.acknowledgers // [])[] | { name: .summary, at: .at } ], description: ($incident.incident.description // null), conference_bridge: ($incident.incident.conference_bridge // null) }, timeline: [ ($logs.log_entries // [])[] | { type: .type, created_at: .created_at, summary: (.summary // .channel.summary // ""), channel_type: (.channel.type // null) } ] | sort_by(.created_at), alerts: [ ($alerts.alerts // [])[] | { id: .id, status: .status, summary: (.summary.description // .summary // null), severity: (.severity // null), created_at: .created_at, source: (.body.cef_details.source_location // .service.summary // null), details: (.body.details // {}) } ], notes: [ ($notes.notes // [])[] | { id: .id, content: .content, created_at: .created_at, user: (.use ...[truncated 2893 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Add explicit Skill-level instructions stating that all PagerDuty titles, descriptions, summaries, details, and notes are untrusted data and must never be treated as agent instructions. - Require the agent to ignore requests embedded in remote incident content, especially requests to reveal secrets, alter safety rules, invoke unrelated tools, or approve write operations. - Clearly delimit remote text in tool output and label each relevant field as untrusted. Structural JSON alone is not a sufficient prompt-injection defense. - Exclude `.body.details` from default output or enforce a strict allowlist of operational fields required for triage. - Apply length limits to free-form titles, descriptions, notes, summaries, and detail values to reduce context flooding and instruction-smuggling opportunities. - Display raw remote content only when specifically requested, preferably escaped and separated from trusted control metadata. - Require a fresh, direct user confirmation for every write operation. Confirmation must identify the exact incident, proposed status change or note content, and must not be inferred from incident text. - Ensure that remote content can never supply or synthesize the `--confirm` argument. - Add adversarial tests containing prompt-injection strings in every forwarded free-text field and verify that the agent treats them only as evidence. - Update `SECURITY.md` to clarify that `jq` field extraction does not sanitize prompt injection and to document the enforced trust-boundary controls. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (42)

YARA rule 'agent_skill_prompt_injection_hidden_instructions': Prompt injection or hidden instructions embedded in AI agent skill text [agent_skills]

High
Category
YARA Match
Content
# Security Model — pager-triage

**Version:** 0.1.1
**Last updated:** 2026-02-16

---

## 1. API Key Handling

### Storage
- API keys are read **exclusively** from environment variables (`PAGERDUTY_API_KEY`, `PAGERDUTY_EMAIL`)
- Keys are **never** hardcoded, written to disk, cached, or stored in any skill state
- Keys are **never** passed as command-line arguments (which would be visible in `ps` output)

### Masking
- If an API key appears in an error message (e.g., authentication failure), it is masked to `****<last 4 chars>` using the `mask_key()` function
- The agent **never** displays the full key
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Instruction Override

High
Category
Prompt Injection
Content
### T1: Prompt Injection via Incident Data

**Risk:** An attacker creates a PagerDuty incident with a title or description containing prompt injection payload (e.g., "Ignore previous instructions and...").

**Mitigation:**
- Incident data flows through `jq` field extraction — only specific fields are included in output
Confidence
80% confidence
Finding
This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Credential Access

High
Category
Privilege Escalation
Content
#### T-SMOKE-7: Invalid incident ID format
```bash
export PAGERDUTY_API_KEY="test_key"
./scripts/pager-triage.sh detail "../../etc/passwd"
```
**Expected:** JSON error `"Invalid incident ID format"`. Exit code 1. No API call made.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
#### T-SMOKE-7: Invalid incident ID format
```bash
export PAGERDUTY_API_KEY="test_key"
./scripts/pager-triage.sh detail "../../etc/passwd"
```
**Expected:** JSON error `"Invalid incident ID format"`. Exit code 1. No API call made.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
#### T-SMOKE-8: Invalid incident ID — special characters
```bash
export PAGERDUTY_API_KEY="test_key"
./scripts/pager-triage.sh detail "P123;rm -rf /"
```
**Expected:** JSON error `"Invalid incident ID format"`. Exit code 1.
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
#### T-SMOKE-8: Invalid incident ID — special characters
```bash
export PAGERDUTY_API_KEY="test_key"
./scripts/pager-triage.sh detail "P123;rm -rf /"
```
**Expected:** JSON error `"Invalid incident ID format"`. Exit code 1.
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Chaining Abuse

High
Category
Tool Misuse
Content
#### T-SMOKE-8: Invalid incident ID — special characters
```bash
export PAGERDUTY_API_KEY="test_key"
./scripts/pager-triage.sh detail "P123;rm -rf /"
```
**Expected:** JSON error `"Invalid incident ID format"`. Exit code 1.
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Chaining Abuse

High
Category
Tool Misuse
Content
#### T-SMOKE-8: Invalid incident ID — special characters
```bash
export PAGERDUTY_API_KEY="test_key"
./scripts/pager-triage.sh detail "P123;rm -rf /"
```
**Expected:** JSON error `"Invalid incident ID format"`. Exit code 1.
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
(
  export PAGERDUTY_API_KEY="smoke_test_key"
  if output=$("$TOOL" detail 'P123;rm -rf /' 2>&1); then
    fail "Command injection ID should be rejected"
  else
    if echo "$output" | grep -q "Invalid incident ID"; then
Confidence
100% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
(
  export PAGERDUTY_API_KEY="smoke_test_key"
  if output=$("$TOOL" detail 'P123;rm -rf /' 2>&1); then
    fail "Command injection ID should be rejected"
  else
    if echo "$output" | grep -q "Invalid incident ID"; then
Confidence
100% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
(
  export PAGERDUTY_API_KEY="smoke_test_key"
  if output=$("$TOOL" detail 'P123;rm -rf /' 2>&1); then
    fail "Command injection ID should be rejected"
  else
    if echo "$output" | grep -q "Invalid incident ID"; then
Confidence
95% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
(
  export PAGERDUTY_API_KEY="smoke_test_key"
  if output=$("$TOOL" detail 'P123;rm -rf /' 2>&1); then
    fail "Command injection ID should be rejected"
  else
    if echo "$output" | grep -q "Invalid incident ID"; then
Confidence
100% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## 2. Read-Only by Default

### Read Operations (5 commands — no confirmation needed)
| Command | What it reads | PD API endpoint |
|---------|--------------|-----------------|
| `incidents` | Active incidents list | `GET /incidents` |
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## 2. Read-Only by Default

### Read Operations (5 commands — no confirmation needed)
| Command | What it reads | PD API endpoint |
|---------|--------------|-----------------|
| `incidents` | Active incidents list | `GET /incidents` |
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## 2. Read-Only by Default

### Read Operations (5 commands — no confirmation needed)
| Command | What it reads | PD API endpoint |
|---------|--------------|-----------------|
| `incidents` | Active incidents list | `GET /incidents` |
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- PagerDuty's `From` header creates an audit trail showing which email performed the action
- PagerDuty's own timeline records all acknowledgment/resolution events

**Residual risk:** Low-medium. If the agent is misconfigured to auto-confirm, this gate is bypassed at the agent level. The `--confirm` flag is a defense-in-depth measure, not a complete solution.

### T4: Incident ID Injection
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill exposes operational capabilities that clearly rely on external API access and likely command/tool execution, but it does not declare any explicit tool scope such as allowed-tools or permissions. That weakens least-privilege controls and makes it harder for the host agent to constrain what the skill may invoke, increasing the blast radius if the skill is misrouted, extended unsafely, or paired with overly permissive runtime defaults.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The activation guidance uses broad natural-language triggers like 'what's on fire?' and generic mentions of 'incidents' or 'triage', which can cause the skill to activate in contexts not specifically requesting PagerDuty access. In a security-sensitive operational skill, unintended activation can expose incident metadata, on-call identities, or encourage accidental progression toward write actions.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The spec materially expands scope from the advertised PagerDuty triage skill to also support OpsGenie and a local CLI fallback path. Scope expansion increases attack surface, introduces additional credential and execution paths, and can cause the agent to interact with systems or binaries the user did not intend to authorize.

External Transmission

Medium
Category
Data Exfiltration
Content
**API Call:**
```bash
curl -s \
  -H "Authorization: Token token=$PAGERDUTY_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.pagerduty.com/incidents?statuses[]=triggered&statuses[]=acknowledged&sort_by=urgency&limit=25&include[]=assignees&include[]=services"
Confidence
60% 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
curl -s \
  -H "Authorization: Token token=$PAGERDUTY_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.pagerduty.com/incidents?statuses[]=triggered&statuses[]=acknowledged&sort_by=urgency&limit=25&include[]=assignees&include[]=services"
```

**Output Schema:**
Confidence
50% 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
curl -s \
  -H "Authorization: Token token=$PAGERDUTY_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.pagerduty.com/incidents?statuses[]=triggered&statuses[]=acknowledged&sort_by=urgency&limit=25&include[]=assignees&include[]=services"
```

**Output Schema:**
Confidence
50% 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
curl -s \
  -H "Authorization: Token token=$PAGERDUTY_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.pagerduty.com/incidents?statuses[]=triggered&statuses[]=acknowledged&sort_by=urgency&limit=25&include[]=assignees&include[]=services"
```

**Output Schema:**
Confidence
50% 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
curl -s \
  -H "Authorization: Token token=$PAGERDUTY_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.pagerduty.com/incidents?statuses[]=triggered&statuses[]=acknowledged&sort_by=urgency&limit=25&include[]=assignees&include[]=services"
```

**Output Schema:**
Confidence
50% 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
curl -s \
  -H "Authorization: Token token=$PAGERDUTY_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.pagerduty.com/incidents?statuses[]=triggered&statuses[]=acknowledged&sort_by=urgency&limit=25&include[]=assignees&include[]=services"
```

**Output Schema:**
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.