T01 · Skill Instruction Hijacking
- Location
scripts/prepare-digest.js:298- Finding
Unsigned Remote Prompts Can Hijack Agent Instructions
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly matches its GitHub digest purpose, but it warrants Review because it stores service credentials locally, installs scheduled jobs, and can load remote prompt instructions without integrity checks.
Install only if you are comfortable with a scheduled local tool that reads a GitHub token, may store Telegram or Resend keys, and sends digest content to configured delivery services. Keep prompts.remoteUrl unset unless you fully trust and control that source, restrict ~/.follow-github permissions, use narrowly scoped expiring tokens, and review any cron job it creates.
scripts/prepare-digest.js:298Unsigned Remote Prompts Can Hijack Agent Instructions
scripts/prepare-digest.js:145Attacker-Controlled GitHub Metadata Is Passed to the LLM Without a Prompt-Injection Boundary
SKILL.md:87Credential File Creation Does Not Enforce Restrictive Filesystem Permissions
SKILL.md:342Predictable Shared Temporary File Enables Symlink Clobbering and Digest Disclosure
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
let remaining = text;
while (remaining.length > 0) {
if (remaining.length <= MAX_LEN) {
chunks.push(remaining);
break;
}
let splitAt = remaining.lastIndexOf('\n', MAX_LEN);
if (splitAt < MAX_LEN * 0.5) splitAt = MAX_LEN;
chunks.push(remaining.slice(0, splitAt));
remaining = remaining.slice(splitAt);
}
for (const chunk of chunks) {
const res = await fetch(
`https://api.telegram.org/bot${botToken}/sendMessage`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text: chunk,
parse_mode: 'Markdown',
disable_web_page_preview: true
})
}
);
if (!res.ok) {
const err = await res.json();
if (err.description && err.description.includes("can't parse")) {
await fetch(
`https://api.telegram.org/bot${botToken}/sendMessage`,
{
method: 'POST',
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result'][0]['message']['chat']['id'])" 2>/dev/null || echo "No messages found — send a message to your bot first"
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result'][0]['message']['chat']['id'])" 2>/dev/null || echo "No messages found — send a message to your bot first"
The skill is presented as a digest generator, but it also documents persistent local state management and a configurable remote prompt source via prompts.remoteUrl. Allowing remote prompt content introduces a code/data trust boundary that is not disclosed in the user-facing purpose and could let an attacker alter future behavior through external content.
The skill is presented as a digest generator, but it also documents persistent local state management and a configurable remote prompt source via prompts.remoteUrl. Allowing remote prompt content introduces a code/data trust boundary that is not disclosed in the user-facing purpose and could let an attacker alter future behavior through external content.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
All data is fetched **live** from GitHub's API and `github.com/trending` —
there is no central feed. Each user runs their own fetches with their own
GitHub Personal Access Token.
## Detecting Platform
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
All data is fetched **live** from GitHub's API and `github.com/trending` —
there is no central feed. Each user runs their own fetches with their own
GitHub Personal Access Token.
## Detecting Platform
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
All data is fetched **live** from GitHub's API and `github.com/trending` —
there is no central feed. Each user runs their own fetches with their own
GitHub Personal Access Token.
## Detecting Platform
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
All data is fetched **live** from GitHub's API and `github.com/trending` —
there is no central feed. Each user runs their own fetches with their own
GitHub Personal Access Token.
## Detecting Platform
The skill directs users to store long-lived credentials in a plaintext .env file under the home directory. Plaintext secret storage increases the risk of credential theft via local compromise, overbroad file access by other tools, backups, or accidental disclosure through later file reads.
The token is saved to ~/.follow-github/.env — never committed to git, never
sent anywhere except GitHub's own API."
Create the .env file:
mkdir -p ~/.follow-github
cat > ~/.follow-github/.env << 'ENVEOF'
Explicitly writing GITHUB_TOKEN into ~/.follow-github/.env normalizes insecure handling of secrets and leaves a reusable authentication token on disk. Even read-only public scopes still enable API consumption tied to the user's identity and can aid profiling or abuse if stolen.
Create the .env file:
mkdir -p ~/.follow-github
cat > ~/.follow-github/.env << 'ENVEOF'
# GitHub Personal Access Token (read-only public access)
GITHUB_TOKEN=paste_your_token_here
The .env template encourages storage of multiple secrets—GitHub, Telegram, and Resend—in one plaintext file. Consolidating several high-value credentials in a single predictable location increases blast radius if that file is read, copied, or leaked.
mkdir -p ~/.follow-github
cat > ~/.follow-github/.env << 'ENVEOF'
# GitHub Personal Access Token (read-only public access)
GITHUB_TOKEN=paste_your_token_here
# Telegram bot token (only if using Telegram delivery — set up in Step 7)
The skill pipes untrusted network data from curl directly into python for parsing in a shell one-liner. While the inline Python here is static, this pattern still expands attack surface and normalizes remote-data-to-interpreter pipelines that are brittle, hard to review, and unsafe to encourage in agent skills.
curl -s "https://api.telegram.org/bot<TOKEN>/getUpdates" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result'][0]['message']['chat']['id'])" 2>/dev/null || echo "No messages found — send a message to your bot first"
Save chat ID as delivery.chatId.
The skill explicitly instructs the agent to read and display prompt files on request. Prompt files may contain sensitive operational instructions, internal guardrails, or remotely sourced content that can be exfiltrated to the user, enabling prompt extraction and increasing the chance of downstream manipulation.
- "Show my settings" → Read and display config.json in a friendly format
- "Who am I following?" → Tell them to check https://github.com/<their username>?tab=following
(we don't cache the following list — it's fetched fresh each digest)
- "Show my prompts" → Read and display prompt files
After any configuration change, confirm what you changed.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
// node deliver.js --file /path/to/digest.txt
//
// The script reads delivery config from ~/.follow-github/config.json
// and API keys from ~/.follow-github/.env
// ============================================================================
import { readFile } from 'fs/promises';
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-github');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const ENV_PATH = join(USER_DIR, '.env');
async function getDigestText() {
const args = process.argv.slice(2);
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-github');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const ENV_PATH = join(USER_DIR, '.env');
async function getDigestText() {
const args = process.argv.slice(2);
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-github');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const ENV_PATH = join(USER_DIR, '.env');
async function getDigestText() {
const args = process.argv.slice(2);
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-github');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const ENV_PATH = join(USER_DIR, '.env');
async function getDigestText() {
const args = process.argv.slice(2);
The lockfile pins transitive dependency undici to 7.25.0, and the provided advisory data indicates multiple known HIGH-severity issues affecting that version. Because this skill fetches GitHub content and likely makes outbound HTTP requests, a vulnerable HTTP client library can expose the fetcher to response desynchronization, cross-user data leakage, request/response mix-ups, or denial of service when interacting with attacker-controlled or intermediary network endpoints.
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
// Prune entries older than 30 days
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
for (const [id, ts] of Object.entries(state.seenEvents)) {
if (ts < cutoff) delete state.seenEvents[id];
}
for (const [id, ts] of Object.entries(state.seenHotRepos)) {
if (ts < cutoff) delete state.seenHotRepos[id];
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
// Prune entries older than 30 days
const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
for (const [id, ts] of Object.entries(state.seenEvents)) {
if (ts < cutoff) delete state.seenEvents[id];
}
for (const [id, ts] of Object.entries(state.seenHotRepos)) {
if (ts < cutoff) delete state.seenHotRepos[id];
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.
errors.push(`prompt missing: ${filename}`);
}
}
return prompts;
}
// -- Main --------------------------------------------------------------------
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
If you choose email delivery, the agent should guide you through:
1. Create a free account at https://resend.com
2. Create an API key in the Resend dashboard
3. Save it as `RESEND_API_KEY` in `~/.follow-github/.env`
4. Put your destination email address into `config.delivery.email`
The skill requires environment access and performs network operations, but it does not declare an explicit tool scope such as permissions or allowed-tools. This weakens the security boundary by making powerful capabilities implicit, reducing reviewability and increasing the chance the skill is invoked with broader access than users expect.
Detected: suspicious.env_credential_access