Back to skill

Security audit

backstage companion

Security checks for vulnerabilities and agentic risk

Overview

This workflow skill is disclosed as an admin helper, but it can run project-provided scripts, apply unpinned remote updates, and control the editor in ways that need careful review.

Install only if you control the projects and the upstream backstage repository. Before use, review every global and local check script, treat project-local Markdown checks as untrusted input, avoid invoking it from untrusted repositories, and be aware that the update command can overwrite or delete files in checks/global despite the displayed wording.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
checks.sh:74
Finding
Automatic Execution of Project-Controlled Shell Scripts<![CDATA[ ## Vulnerability Details **File Location**: `checks.sh`, lines 23-35 and 74-85 **Vulnerability Type**: Arbitrary execution of untrusted project scripts **Risk Level**: Critical ### Code Snippet ```bash LOCAL_CHECKS_DIR="backstage/checks/local" # Collect local check basenames (for override detection) LOCAL_CHECKS="" if [ -d "$LOCAL_CHECKS_DIR" ]; then for check in "$LOCAL_CHECKS_DIR"/*.sh; do if [ -f "$check" ]; then basename_check=$(basename "$check") LOCAL_CHECKS="$LOCAL_CHECKS $basename_check " fi done fi ``` ```bash # Run local checks (always run, overrides global if same name) if [ -d "$LOCAL_CHECKS_DIR" ]; then echo " 📋 Local checks:" for check in "$LOCAL_CHECKS_DIR"/*.sh; do if [ -f "$check" ]; then basename_check=$(basename "$check") # Run check if bash "$check" >/dev/null 2>&1; then echo " ✅ $basename_check" else echo " ❌ $basename_check (failed)" CHECKS_PASS=false fi ``` ### Technical Analysis The check runner automatically executes every shell script under the target project's `backstage/checks/local/` directory. These files are controlled by the project and may therefore be supplied by any contributor with permission to add repository files. There is no script allowlist, integrity manifest, signature verification, content inspection, sandbox, or per-script execution confirmation. The scripts inherit the privileges, environment, filesystem access, network access, and credentials available to the user running the Skill. Redirecting both standard output and standard error to `/dev/null` further reduces transparency by hiding payload output and error messages. ### Attack Path 1. An attacker adds a file such as `backstage/checks/local/health.sh` to a repository. 2. The file contains arbitrary shell commands, such as commands that read credentials, alter sourc ...[truncated 918 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not automatically execute shell scripts supplied by the current project. 2. Replace executable checks with a narrowly defined declarative validation format. 3. If shell checks are unavoidable, maintain a trusted manifest containing approved paths and cryptographic hashes. 4. Display each script's path, content, and relevant version-control diff before requesting explicit execution approval. 5. Require approval separately for each newly added or modified script. 6. Execute approved checks in an isolated environment with: - Read-only project access where possible - No home-directory access - No inherited secrets or credentials - Network access disabled by default - Resource and execution-time limits 7. Do not suppress script output. Capture and clearly attribute stdout, stderr, and exit status. 8. Reject symlinks and verify that every resolved script path remains inside the intended checks directory. ]]>

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:137
Finding
Project-Controlled Markdown Can Override Agent Instructions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 137-145 and 245-248 **Vulnerability Type**: Untrusted instruction execution and local policy override **Risk Level**: Critical ### Code Snippet ```markdown 1. **Checks (Interpretive)** - `checks/global/*.md` = Universal workflow rules - `checks/local/*.md` = Project-specific overrides - **Enforced by:** AI (reads markdown, interprets context, acts) - **Always pass:** AI reads, understands, will act accordingly 2. **Checks (Deterministic)** - `checks/global/*.sh` = Universal validation tests - `checks/local/*.sh` = Project-specific tests - **Enforced by:** Bash (executes shell scripts, exit codes) ``` ```markdown - Reads ALL `checks/**/*.md` files (global + local) - Executes ALL `checks/**/*.sh` files (global + local) - Merges checks when compatible - Prefers local checks on conflict ``` ### Technical Analysis The Skill instructs the Agent to read project-controlled Markdown as operational instructions and explicitly gives local Markdown precedence when it conflicts with global rules. This crosses the trust boundary between repository data and authoritative Agent instructions. No schema restricts local Markdown to passive validation metadata. No filtering prevents it from containing commands to access unrelated files, disclose information, modify additional resources, ignore warnings, or alter the workflow. Marking interpretive checks as always passing also prevents meaningful validation of their safety. This creates a prompt-injection channel in which a repository contributor can influence the Agent's session objectives merely by adding or modifying a Markdown file in the expected directory. ### Attack Path 1. An attacker adds a malicious Markdown file under `backstage/checks/local/`. 2. The file presents hostile instructions as a project-specific workflow rule. 3. A victim invokes a supported Backstage trigger. 4. The Skill directs the Agent to read all local ...[truncated 870 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all repository Markdown as untrusted data, not Agent instructions. 2. Replace free-form interpretive checks with a strict declarative schema containing only supported validation properties. 3. Parse and validate that schema programmatically; reject unknown fields and embedded operational instructions. 4. Never permit project content to override system, developer, user, or Skill-level safety constraints. 5. Display proposed project rules to the user and require explicit approval before applying them. 6. Constrain approved rules to the current repository and deny requests involving unrelated files, credentials, network destinations, or session-level behavior. 7. Clearly label quoted repository content as untrusted when presenting it to the Agent. 8. Remove the rule that interpretive checks “always pass”; report rejected, malformed, or unsafe rules explicitly. ]]>

T03 · Remote Payload Retrieval and Execution

Error
Location
update-backstage.sh:40
Finding
Unpinned Remote Scripts Are Installed and Subsequently Executed<![CDATA[ ## Vulnerability Details **File Location**: `update-backstage.sh`, lines 40-45 and 138-139; `checks.sh`, lines 39-55 **Vulnerability Type**: Mutable remote payload retrieval and execution **Risk Level**: Critical ### Code Snippet ```bash # Fetch latest from upstream echo "" echo "🔄 Fetching latest from upstream..." TMP_DIR=$(mktemp -d) git clone --quiet --depth 1 "$UPSTREAM" "$TMP_DIR/backstage" 2>/dev/null || { echo "❌ Failed to clone upstream (offline or repo moved?)" ``` ```bash # Update echo "" echo "🔄 Updating checks/global/..." rsync -av --delete "$TMP_DIR/backstage/backstage/checks/global/" "$BACKSTAGE_DIR/checks/global/" >/dev/null ``` The synchronized global scripts are subsequently executed by `checks.sh`: ```bash # Run global checks (skip if local has same name) if [ -d "$GLOBAL_CHECKS_DIR" ]; then echo " 📋 Global checks:" for check in "$GLOBAL_CHECKS_DIR"/*.sh; do if [ -f "$check" ]; then basename_check=$(basename "$check") # Skip if local overrides (check if basename is in LOCAL_CHECKS string) if echo "$LOCAL_CHECKS" | grep -q " $basename_check "; then echo " ⏭️ $basename_check (local override)" continue fi # Run check CHECK_OUTPUT=$(bash "$check" 2>&1) ``` ### Technical Analysis The updater clones the mutable default branch of `https://github.com/nonlinear/backstage` without pinning an immutable commit or release. It does not verify a release signature, commit signature, cryptographic hash manifest, or trusted publisher key. Files from the clone are copied into a globally trusted checks directory. The check runner later executes every shell script in that directory. Consequently, the effective executable payload can change after the Skill package has been reviewed. Although the updater asks for confirmation, the displayed summary primarily identifies filenames and optional self-authored description c ...[truncated 1368 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin updates to immutable release commits rather than cloning the current default branch. 2. Verify signed commits or signed release artifacts against a locally configured trusted publisher key. 3. Publish a cryptographic manifest and validate every downloaded file before installation. 4. Show complete diffs for all executable changes and require explicit approval before installing them. 5. Separate Markdown policy updates from executable script updates; executable changes should require stronger review. 6. Stage updates in a non-executable quarantine directory until verification is complete. 7. Run downloaded scripts only in a restricted sandbox without credentials, home-directory access, or network access by default. 8. Record the installed commit identifier and hashes so later runs can verify integrity. 9. Provide a rollback mechanism and preserve the prior verified version. 10. Avoid globally trusting all files solely because they came from a named GitHub repository. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
update-backstage.sh:139
Finding
Destructive Synchronization Can Delete Local Check Files<![CDATA[ ## Vulnerability Details **File Location**: `update-backstage.sh`, line 139 **Vulnerability Type**: Destructive directory synchronization **Risk Level**: Medium ### Code Snippet ```bash rsync -av --delete "$TMP_DIR/backstage/backstage/checks/global/" "$BACKSTAGE_DIR/checks/global/" >/dev/null ``` ### Technical Analysis The updater invokes `rsync` with `--delete`, causing destination entries that are absent from the remote source to be removed. This is inconsistent with the preceding update message that describes removed upstream files as follows: ```text - $file (will be kept locally unless you delete) ``` The implementation therefore provides a misleading safety expectation. Once the user approves the broad update operation, locally retained or customized files in the selected global checks directory can be deleted. The project directory is selected using the first matching `backstage` directory returned by `find`, increasing the possibility that the operation targets a different matching directory than the user intended. ### Attack Path 1. A project contains local additions or customized files in `backstage/checks/global/`. 2. Those files are absent from the cloned upstream directory. 3. The updater reports that removed files will be kept locally. 4. The user approves the update based on that representation. 5. `rsync --delete` removes destination entries that do not exist upstream. 6. The local files are lost unless recoverable through version control or backups. ### Impact Assessment The direct impact is deletion of files inside the selected `checks/global` destination directory. This may remove local security checks, workflow controls, or uncommitted customizations. The command does not, by itself, demonstrate deletion outside that destination path. However, removal of security checks can weaken later validation and create operational disruption. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--delete` unless deletion is an explicitly selected operation. 2. Make the user-facing summary accurately describe every file that will be deleted. 3. Require separate confirmation for deletions after displaying exact relative paths. 4. Back up or version the destination directory before applying an update. 5. Verify the resolved destination path and require the user to confirm that exact path. 6. Reject symlinks and ensure the canonical destination remains within the intended project. 7. Use a staged merge process that preserves local-only files by default. 8. If authoritative mirroring is required, provide a dry-run using `rsync --dry-run --itemize-changes` before confirmation. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
SKILL.md:423
Finding
Forced Application Shutdown and Suppression of Completion Reporting<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 423-426 **Vulnerability Type**: Session behavior override and disruptive operating-system action **Risk Level**: Medium ### Code Snippet ```markdown **Close VS Code 🌙:** Run countdown + `osascript -e 'quit app "Visual Studio Code"'`. CRITICAL: Agent must NOT send ANY message after this or VS Code will prompt "unsaved changes". - **Code:** `backstage-end.sh::close_vscode()` **[STAY SILENT]:** No reply after closing VS Code (prevents unsaved prompt). ``` ### Technical Analysis The Skill directs the Agent to terminate Visual Studio Code through an operating-system command and then suppress all subsequent output. Application shutdown is not necessary for project status validation and can disrupt unrelated editor sessions. The silence requirement interferes with normal transparency and completion reporting. It can prevent the user from receiving confirmation, errors, warnings, or a record of preceding actions. The referenced `backstage-end.sh` is not present in the audited project, but the instruction itself directs the Agent to perform the behavior. ### Attack Path 1. The user invokes an end-session trigger supported by the Skill. 2. The Agent follows the Skill instruction to execute `osascript` and quit Visual Studio Code. 3. The editor closes, potentially affecting unrelated windows or workspaces. 4. The Agent follows the mandatory silence instruction. 5. The user receives no final report describing what was executed or whether earlier operations succeeded. ### Impact Assessment The action can interrupt active development sessions and may expose unsaved work to application-specific close behavior. Its scope is the Visual Studio Code application running in the user's graphical session. The output-suppression requirement reduces auditability and may hide failures or security-relevant outcomes. No privilege escalation is demonstrated; the command operates with the current user's norma ...[truncated 28 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic application shutdown from the standard workflow. 2. If editor closure is desired, make it a separate, explicit, opt-in action. 3. Display the exact command and request immediate confirmation before execution. 4. Do not apply the action to broad conversational triggers such as ordinary session-ending phrases. 5. Never suppress the final Agent response. 6. Always report commands executed, their exit status, and any failures or skipped operations. 7. Prefer advising the user to close the editor manually rather than controlling a desktop application. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill includes a remote update workflow that clones from GitHub and rsyncs content into local project directories, yet this is not reflected in the narrow anti-drift description. Remote code/content synchronization into trusted local paths materially increases supply-chain risk, especially when updates affect enforcement scripts later executed locally.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill includes a remote update workflow that clones from GitHub and rsyncs content into local project directories, yet this is not reflected in the narrow anti-drift description. Remote code/content synchronization into trusted local paths materially increases supply-chain risk, especially when updates affect enforcement scripts later executed locally.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill includes a remote update workflow that clones from GitHub and rsyncs content into local project directories, yet this is not reflected in the narrow anti-drift description. Remote code/content synchronization into trusted local paths materially increases supply-chain risk, especially when updates affect enforcement scripts later executed locally.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill includes a remote update workflow that clones from GitHub and rsyncs content into local project directories, yet this is not reflected in the narrow anti-drift description. Remote code/content synchronization into trusted local paths materially increases supply-chain risk, especially when updates affect enforcement scripts later executed locally.

Vague Triggers

High
Confidence
97% confidence
Finding
Trigger phrases such as 'good morning', 'bom dia', 'whatsup', and similar everyday language are overly broad for a skill that can execute checks, modify files, commit, push, fetch remote content, and close applications. Broad natural-language activation creates a high risk of accidental invocation during normal conversation, causing unintended privileged operations.

Vague Triggers

High
Confidence
97% confidence
Finding
Additional examples like 'what's the status', 'wrap up', and 'pause work' are ambiguous and likely to appear in ordinary dialogue. In the context of a privileged skill, ambiguous triggers amplify the chance that the agent interprets casual text as authorization to run shell scripts, alter repositories, or terminate applications.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The script explicitly tells the user that files removed upstream will be kept locally unless manually deleted, but then performs rsync -av --delete, which deletes those local files. This is a direct integrity and trust violation: a user can approve the update based on false assurances and lose local files, including custom checks or safeguards, without realizing the script will remove them.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest frames the skill as a parity/health-check tool, but the body documents remote updates, file rewriting, commit/push actions, and application control. Security-sensitive behaviors hidden behind a benign description increase the chance of unsafe invocation and misinformed consent by users and automated tooling.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
Quitting Visual Studio Code via `osascript` is an OS/application-control action unrelated to the stated anti-drift objective and can disrupt active user work. The added instruction to remain silent afterward is especially suspicious because it suppresses visibility after a state-changing action that could cause data loss or hinder user response.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The claim that the skill has no hardcoded paths and works on any project is contradicted by references to fixed absolute locations and global path execution. Misrepresenting environmental assumptions is dangerous because users may deploy it in contexts where it reads or executes from unexpected locations, weakening review and containment.

Skill Enumeration

Medium
Category
Agent Snooping
Content
**Created:** 2026-02-12
**Updated:** 2026-02-18 (v1.0.0 - modular checks/checks)
**Status:** Documentation updated, scripts pending
**Location:** `~/Documents/backstage/skills/backstage/SKILL.md`
Confidence
80% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest says this skill is an 'Anti-drift protocol script' triggered by greeting a project to load project context with health checks. In contrast, this file presents itself as a 'Pre-Push Validation' and 'Pre-commit Check' flow, prompting the user about being 'Ready to commit and push' and reporting 'SAFE TO PUSH,' which is a materially different user-facing behavior than passive context loading or parity checking.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The script executes every matching .sh file from both a user-global directory and a project-local directory, which is far broader than a passive docs/system parity check. Because this runs during the skill's startup flow, any attacker who can place or modify a script in those locations gains arbitrary code execution in the user's environment.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The skill executes arbitrary shell scripts with no confirmation, no warning, and no trust boundary checks, creating silent code-execution behavior. This is amplified by the skill metadata, which frames the feature as a benign parity/health-check utility rather than an execution mechanism, increasing the chance of unsafe use on untrusted repositories.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
Running project-local shell scripts from backstage/checks/local means simply opening or using a repository can trigger repository-supplied code. In the context of an agent skill activated by a greeting-style trigger, this is especially dangerous because users may not expect code execution from untrusted project content.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The header says the script syncs local checks/global with upstream, but the implementation uses rsync --delete, which removes local files absent from upstream. This is dangerous because operators may rely on the stated behavior and unintentionally lose local customizations or security controls when running what appears to be a simple update.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill metadata presents this as a docs/system parity health-check helper, but the script actually clones a remote repository and synchronizes local files into the project. That mismatch is security-relevant because users may invoke it expecting read-only inspection, while it performs network access and modifies trusted local content, increasing the chance of unintended supply-chain changes.

Intent-Code Divergence

Low
Confidence
90% confidence
Finding
L364 says the README navigation block is the only source of truth for file locations. However, L030, L367, L429, and L486 describe using fixed global paths or filesystem search to locate backstage resources, which contradicts the exclusivity of the README-based mechanism.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The header comments describe the file as a minimal orchestrator whose intelligence lives in checks/, suggesting limited coordination behavior. However, the script contains additional intent-bearing logic in prompt_push() that frames results as 'SAFE TO PUSH' and asks whether to 'commit and push,' which goes beyond merely orchestrating checks.

Static analysis

No suspicious patterns detected.