T01 · Skill Instruction Hijacking
- Location
scripts/prepare-digest.js:32- Finding
Mutable Remote Prompts Can Modify Agent Instructions After Installation
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill largely matches its digest purpose, but needs Review because it can store delivery keys locally, create scheduled jobs, and follow mutable prompt instructions fetched from GitHub.
Install only if you are comfortable with a digest skill that can set up recurring jobs and optionally send messages through your Telegram bot or Resend account. Prefer in-chat/stdout delivery if you do not need unattended delivery; if using Telegram or email, restrict ~/.follow-builders permissions, protect and rotate the tokens, and review any cron job it creates. The biggest review item is that digest prompts can update from GitHub after installation, so the skill's summarization instructions can change without a new package review.
scripts/prepare-digest.js:32Mutable Remote Prompts Can Modify Agent Instructions After Installation
SKILL.md:134Credential File Is Created Without Explicit Restrictive Permissions
SKILL.md:345Predictable Shared Temporary File Creates Symlink and Digest Disclosure Risk
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
h > 0) {
if (remaining.length <= MAX_LEN) {
chunks.push(remaining);
break;
}
// Try to split at a newline near the limit
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 Markdown parsing fails, retry without parse_mode
if (err.description && err.description.includes("can't parse")) {
await fetch(
`https://api.telegram.org/bot${botToken}/
The declared purpose is content curation, but the instructions also collect credentials, read and write local config files, create cron jobs, and send outbound messages and email. This mismatch can mislead users and reviewers about the real trust boundary, causing them to approve a more privileged automation skill than intended.
The instructions direct the user to obtain a Telegram bot token and place it into a local .env file, giving the skill access to a reusable credential. Exposure of this token would let an attacker control the bot's messaging behavior and potentially harvest chat metadata or send spam/phishing from the user's bot.
Then add the token to the .env file. To get the chat ID, run:
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 — make sure you sent a message to your bot first"
The skill uses curl piped into python to process remote Telegram API responses during onboarding. While the Python is inline rather than remotely fetched code, this pattern still combines external network input with command-line processing and normalizes unsafe shell-driven handling of untrusted data and secrets.
Then add the token to the .env file. To get the chat ID, run:
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 — make sure you sent a message to your bot first"
Save the chat ID in config.json under delivery.chatId.
The skill asks for a Resend API key and instructs the user to place it in a local .env file, exposing a credential that can send outbound email on the user's behalf. If leaked, the key could be used for spam, impersonation, or service abuse that impacts account reputation and billing.
3. Go to API Keys in the dashboard
4. Create a new key and copy it
Add the key to the .env file.
**If they choose on-demand:**
Set `delivery.method` to `"stdout"`. Tell them: "No problem — just type /ai
The explicit creation of ~/.follow-builders/.env establishes a predictable plaintext secret location for delivery credentials. Predictable secret paths are easier for other local tools, malicious processes, or accidental sync/backup systems to discover and exfiltrate.
All content is fetched centrally. Skip to Step 6.
If the user chose Telegram or Email delivery: Create the .env file with only the delivery key they need:
mkdir -p ~/.follow-builders
The sample .env content explicitly includes placeholders for TELEGRAM_BOT_TOKEN and RESEND_API_KEY, normalizing insecure secret handling and encouraging users to paste high-value credentials into a local file. This increases the likelihood of credential exposure through file access, backups, or support sharing.
mkdir -p ~/.follow-builders
cat > ~/.follow-builders/.env << 'ENVEOF'
# Telegram bot token (only if using Telegram delivery)
# TELEGRAM_BOT_TOKEN=paste_your_token_here
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-builders/config.json
// and API keys from ~/.follow-builders/.env
//
// Delivery methods:
// - "telegram": sends via Telegram Bot API (needs TELEGRAM_BOT_TOKEN + chat ID)
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-builders/config.json
// and API keys from ~/.follow-builders/.env
//
// Delivery methods:
// - "telegram": sends via Telegram Bot API (needs TELEGRAM_BOT_TOKEN + chat ID)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-builders');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const STATE_PATH = join(USER_DIR, 'state.json');
const ENV_PATH = join(USER_DIR, '.env');
// How far back to look for new content (overridable via --lookback-hours flag)
const DEFAULT_LOOKBACK_HOURS = 24;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-builders');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const STATE_PATH = join(USER_DIR, 'state.json');
const ENV_PATH = join(USER_DIR, '.env');
// How far back to look for new content (overridable via --lookback-hours flag)
const DEFAULT_LOOKBACK_HOURS = 24;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-builders');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const STATE_PATH = join(USER_DIR, 'state.json');
const ENV_PATH = join(USER_DIR, '.env');
// How far back to look for new content (overridable via --lookback-hours flag)
const DEFAULT_LOOKBACK_HOURS = 24;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-builders');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const STATE_PATH = join(USER_DIR, 'state.json');
const ENV_PATH = join(USER_DIR, '.env');
// How far back to look for new content (overridable via --lookback-hours flag)
const DEFAULT_LOOKBACK_HOURS = 24;
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const USER_DIR = join(homedir(), '.follow-builders');
const CONFIG_PATH = join(USER_DIR, 'config.json');
const STATE_PATH = join(USER_DIR, 'state.json');
const ENV_PATH = join(USER_DIR, '.env');
// How far back to look for new content (overridable via --lookback-hours flag)
const DEFAULT_LOOKBACK_HOURS = 24;
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
// Prune entries older than 7 days to prevent the file from growing forever
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
for (const [id, ts] of Object.entries(state.seenTweets)) {
if (ts < cutoff) delete state.seenTweets[id];
}
for (const [id, ts] of Object.entries(state.seenVideos)) {
if (ts < cutoff) delete state.seenVideos[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 7 days to prevent the file from growing forever
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
for (const [id, ts] of Object.entries(state.seenTweets)) {
if (ts < cutoff) delete state.seenTweets[id];
}
for (const [id, ts] of Object.entries(state.seenVideos)) {
if (ts < cutoff) delete state.seenVideos[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 7 days to prevent the file from growing forever
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
for (const [id, ts] of Object.entries(state.seenTweets)) {
if (ts < cutoff) delete state.seenTweets[id];
}
for (const [id, ts] of Object.entries(state.seenVideos)) {
if (ts < cutoff) delete state.seenVideos[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 7 days to prevent the file from growing forever
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
for (const [id, ts] of Object.entries(state.seenTweets)) {
if (ts < cutoff) delete state.seenTweets[id];
}
for (const [id, ts] of Object.entries(state.seenVideos)) {
if (ts < cutoff) delete state.seenVideos[id];
The skill requests environment access and performs network-capable operations, but it does not declare a scoped permission model such as allowed tools or explicit permissions. This weakens reviewability and allows the skill to invoke sensitive capabilities beyond what a user would infer from a digest skill.
The invocation description includes broad trigger phrases like wanting AI industry insights or invoking /ai, which can cause the skill to activate in more contexts than users expect. Overbroad invocation increases the chance of accidental execution of privileged behaviors such as file writes, credential prompts, or scheduling.
The skill collects third-party delivery credentials for Telegram and Resend even though the core advertised function is producing digests. Credential collection materially increases sensitivity because compromise of the skill, logs, or local files could expose tokens usable for external messaging abuse.
The skill transmits data to an external service, the Telegram Bot API, during setup and later delivery. External transmission is security-relevant because it moves data and identifiers outside the local environment and could be abused if content or tokens are mishandled.
Then add the token to the .env file. To get the chat ID, run:
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 — make sure you sent a message to your bot first"
Save the chat ID in config.json under delivery.chatId.
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.
1. Go to https://resend.com
2. Sign up (free tier gives 100 emails/day — more than enough)
3. Go to API Keys in the dashboard
4. Create a new key and copy it
Add the key to the .env file.
The documentation says no API keys are needed for stdout delivery, but the manifest still requires SUPADATA_API_KEY environment access. This inconsistency can cause unnecessary exposure of secrets to a workflow that claims not to need them, violating least privilege.
The skill writes sensitive delivery credentials to ~/.follow-builders/.env in plaintext and does so without prominent security warnings or guidance on filesystem protections. Plaintext local secret storage is risky because other local processes, backups, shell history, or accidental disclosure can expose reusable tokens.
Detected: suspicious.env_credential_access, suspicious.install_untrusted_source