This workflow corresponds to n8n.io template #14543 — we link there as the canonical source.
This workflow follows the Google Drive → Google Drive Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "V7NNklvJYzjo5mkU",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "APK Upload Monitoring and Automated MobSF Analysis with Slack Reporting",
"tags": [],
"nodes": [
{
"id": "72a0121a-1e05-4972-89e4-2e7e280bf128",
"name": "Start Analysis Trigger",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
32,
304
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": "1Rcs1PQWaE2dP1IV5d-Nv1ZsdunIkTUyL",
"cachedResultUrl": "#",
"cachedResultName": "APK Uploads Folder"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "61177280-272f-4584-96fe-562ada79d839",
"name": "Download Apk",
"type": "n8n-nodes-base.googleDrive",
"position": [
256,
304
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "url",
"value": "={{ $json.webViewLink }}"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "5df99ede-a9ec-46a5-a02e-8a4695ec947d",
"name": "MobSF \u2013 Upload APK",
"type": "n8n-nodes-base.httpRequest",
"position": [
544,
304
],
"parameters": {
"url": "http://192.168.101.135:8000/api/v1/upload",
"method": "POST",
"options": {
"redirect": {
"redirect": {}
}
},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "file",
"parameterType": "formBinaryData",
"inputDataFieldName": "data"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "31668bffb4a6ad7b9479420529216e1817f4bad21a2bf04915d4f3bf03fe8b01"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "20136a44-3730-4a13-8770-533ee5a9c10c",
"name": "MobSF \u2013 Start Static Analysis",
"type": "n8n-nodes-base.httpRequest",
"position": [
736,
304
],
"parameters": {
"url": "http://192.168.101.135:8000/api/v1/scan",
"method": "POST",
"options": {
"redirect": {
"redirect": {}
}
},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "hash",
"value": "={{ $json.hash }}"
},
{
"name": "re_scan",
"value": "1"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "31668bffb4a6ad7b9479420529216e1817f4bad21a2bf04915d4f3bf03fe8b01"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "fe98e85e-f609-4d8f-a76e-7cb7cba53364",
"name": "Extract used and detected packages",
"type": "n8n-nodes-base.code",
"position": [
1088,
304
],
"parameters": {
"jsCode": "// This script processes a MobSF JSON report to extract used and detected libraries.\n\nfor (const item of $input.all()) {\n const mobsfReport = item.json.json || item.json;\n\n // List 1: Packages found in code analysis (i.e., \"used\")\n const usedPackages = new Set();\n if (mobsfReport.code_analysis && mobsfReport.code_analysis.findings) {\n for (const key of Object.keys(mobsfReport.code_analysis.findings)) {\n const finding = mobsfReport.code_analysis.findings[key];\n if (finding.files) {\n for (const filePath of Object.keys(finding.files)) {\n const parts = filePath.split('/');\n if (parts.length > 1) {\n parts.pop(); // remove filename\n const packageName = parts.join('.');\n usedPackages.add(packageName);\n }\n }\n }\n }\n }\n\n // List 2: Packages detected from Android components (proxy for \"declared/included\")\n const detectedComponents = [\n ...(mobsfReport.activities || []),\n ...(mobsfReport.receivers || []),\n ...(mobsfReport.providers || []),\n ...(mobsfReport.services || []),\n ...(mobsfReport.libraries || [])\n ];\n\n const detectedPackages = new Set();\n for (const componentPath of detectedComponents) {\n const parts = componentPath.split('.');\n if (parts.length > 1) {\n // Heuristic: if a component name is very long, it's likely a specific class.\n // We take the first 2 or 3 parts as the base package name.\n // e.g., com.google.android.gms.measurement.AppMeasurementReceiver -> com.google.android.gms\n let packageParts;\n if (parts[0] === 'com' && parts[1] === 'google') {\n packageParts = parts.slice(0, 4); // e.g., com.google.android.gms\n } else if (parts[0] === 'androidx') {\n packageParts = parts.slice(0, 2); // e.g., androidx.core\n } else {\n packageParts = parts.slice(0, 2); // e.g., io.livekit\n }\n if (packageParts.length > 0) {\n detectedPackages.add(packageParts.join('.'));\n }\n }\n }\n\n // Overwrite the original json with the new lists\n item.json = {\n usedPackages: Array.from(usedPackages).sort(),\n detectedPackages: Array.from(detectedPackages).sort()\n };\n}\n\n// Return the modified items\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "200dd4a7-35ab-43a5-bd07-b3cc07417899",
"name": "Identify Unused Packages",
"type": "n8n-nodes-base.code",
"position": [
1264,
304
],
"parameters": {
"jsCode": "const item = $input.item.json;\n\nconst used = new Set(item.usedPackages);\nconst detected = item.detectedPackages;\n\nconst unused = detected.filter(pkg => {\n return !Array.from(used).some(u => u.startsWith(pkg));\n});\n\nreturn [{\n json: {\n usedPackages: item.usedPackages,\n detectedPackages: item.detectedPackages,\n unusedPackages: unused\n }\n}];"
},
"typeVersion": 2
},
{
"id": "8ff566ce-d1ea-4ba6-8374-cd9f33b5c7c5",
"name": "Classify Unused Package Risk Levels",
"type": "n8n-nodes-base.code",
"position": [
1456,
304
],
"parameters": {
"jsCode": "const unused = $json.unusedPackages || [];\n\nconst risky = [];\nconst maybe = [];\nconst safe = [];\n\n// Rules\nfor (const lib of unused) {\n\n // RISKY: required by Google / Firebase / Android system\n if (\n lib.startsWith(\"com.google.android.gms\") ||\n lib.startsWith(\"com.google.firebase\") ||\n lib.startsWith(\"androidx.startup\") ||\n lib.startsWith(\"com.google.android.datatransport\")\n ) {\n risky.push(lib);\n continue;\n }\n\n // MAYBE: could be auto-included by Gradle or React Native\n if (\n lib.startsWith(\"com.jakewharton\") ||\n lib.startsWith(\"androidx.profileinstaller\")\n ) {\n maybe.push(lib);\n continue;\n }\n \n // SAFE: camera, mlkit, unused modules, etc.\n safe.push(lib);\n}\n\nreturn [{\n json: {\n unusedPackages: unused,\n safeToRemove: safe,\n maybeRequired: maybe,\n riskyToRemove: risky\n }\n}];"
},
"typeVersion": 2
},
{
"id": "32d70a81-c0e5-49f5-9da0-d8f145c6d659",
"name": "Generate Developer Summary",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1744,
304
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {},
"responses": {
"values": [
{
"content": "=You are an Android performance and security expert.\n\nSummarize the following unused library analysis into a clean developer-friendly message suitable for Slack.\n\nUnused Packages:\n{{JSON.stringify($json.unusedPackages, null, 2)}}\n\nSafe to Remove:\n{{JSON.stringify($json.safeToRemove, null, 2)}}\n\nMaybe Required:\n{{JSON.stringify($json.maybeRequired, null, 2)}}\n\nRisky to Remove:\n{{JSON.stringify($json.riskyToRemove, null, 2)}}\n\nYour output must include:\n- Number of unused packages found\n- List of safe-to-remove packages\n- List of \u201cneeds review\u201d packages\n- List of \u201chigh-risk to remove\u201d packages\n- A short recommendation (2\u20133 lines)\n- No markdown formatting\n\nReturn ONLY the summary message.\n"
}
]
},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "13d216b1-b0d2-48b9-bfaf-3a38463dcffe",
"name": "Notify Team on Slack",
"type": "n8n-nodes-base.slack",
"position": [
2064,
304
],
"parameters": {
"text": "={{ $json.output[0].content[0].text }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09S57E2JQ2",
"cachedResultName": "n8n"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "15eb9ef6-eb61-45d6-94d8-78da000c2125",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-32,
64
],
"parameters": {
"color": 7,
"width": 464,
"height": 416,
"content": "## File Detection & APK Retrieval\nThis section monitors a specific Google Drive folder for new APK uploads. When a file appears, the workflow automatically grabs its details and downloads the APK so it can be processed further. This ensures the analysis begins instantly.)"
},
"typeVersion": 1
},
{
"id": "de1c6c6c-a40a-4fa7-8f4f-b9b35a6c4de3",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
64
],
"parameters": {
"color": 7,
"width": 464,
"height": 416,
"content": "## Upload & Trigger MobSF Static Scan\nAfter downloading, the APK is uploaded to MobSF. Once uploaded successfully, the workflow immediately triggers a static analysis scan using the provided hash, ensuring a smooth handoff between file upload and security scanning steps."
},
"typeVersion": 1
},
{
"id": "f60a4870-8877-4be8-b81b-82e8f8a6e6ce",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1024,
64
],
"parameters": {
"color": 7,
"width": 592,
"height": 416,
"content": "## Extract, Compare & Classify Package\nThis section processes the MobSF report using JavaScript to extract all detected and used packages. It compares both lists to identify unused libraries and then classifies them into safe, maybe-required and high-risk categories. This creates a clear, structured understanding of dependency usage in the app."
},
"typeVersion": 1
},
{
"id": "fff93446-a93b-461b-ac12-96701ab4a1a7",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1696,
64
],
"parameters": {
"color": 7,
"width": 576,
"height": 416,
"content": "## Generate & Deliver Developer Summary\nThis section creates a clean, developer-friendly summary using AI and instantly sends it to the team\u2019s Slack channel. It ensures clear communication of unused package findings and helps developers quickly review risks and decide next steps without manual effort.)"
},
"typeVersion": 1
},
{
"id": "100f9c8a-0022-4eca-81f9-1dd7834da1b4",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-592,
-592
],
"parameters": {
"width": 512,
"height": 832,
"content": "## How It Works\n#### This workflow automatically analyzes newly uploaded APK files and generates a clean summary for the development team. It begins by monitoring a Google Drive folder for new APKs. When a file is added, the workflow downloads it and sends it to MobSF for static analysis. MobSF returns detailed findings, which are processed through JavaScript nodes to extract used, detected and unused packages. These packages are then evaluated and categorized into safe, maybe-required and risky groups. After processing, an AI model turns the results into a simple message that\u2019s easy for developers to understand. Finally, the summary is automatically sent to a Slack channel, ensuring the team gets instant visibility into any unnecessary or risky dependencies.\n\n## Setup Steps\n\n### Connect Google Drive: Add your Google Drive credentials and specify the folder to watch for APK uploads.\n\n### MobSF Configuration: Ensure your MobSF server URL and API key are correctly set in the HTTP Request nodes for upload and scan.\n\n### JavaScript Nodes: The code is already included\u2014no setup required unless you want to modify the logic.\n\n### OpenAI Connection: Add your OpenAI API key to the \u201cGenerate Developer Summary\u201d node.\n\n### Slack Integration: Connect your Slack account and select the target channel.\n\n### Enable the Workflow: Activate the workflow so it starts monitoring and processing files automatically."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "99d4865c-feab-4dc8-915d-830189eb1b80",
"connections": {
"Download Apk": {
"main": [
[
{
"node": "MobSF \u2013 Upload APK",
"type": "main",
"index": 0
}
]
]
},
"MobSF \u2013 Upload APK": {
"main": [
[
{
"node": "MobSF \u2013 Start Static Analysis",
"type": "main",
"index": 0
}
]
]
},
"Start Analysis Trigger": {
"main": [
[
{
"node": "Download Apk",
"type": "main",
"index": 0
}
]
]
},
"Identify Unused Packages": {
"main": [
[
{
"node": "Classify Unused Package Risk Levels",
"type": "main",
"index": 0
}
]
]
},
"Generate Developer Summary": {
"main": [
[
{
"node": "Notify Team on Slack",
"type": "main",
"index": 0
}
]
]
},
"MobSF \u2013 Start Static Analysis": {
"main": [
[
{
"node": "Extract used and detected packages",
"type": "main",
"index": 0
}
]
]
},
"Extract used and detected packages": {
"main": [
[
{
"node": "Identify Unused Packages",
"type": "main",
"index": 0
}
]
]
},
"Classify Unused Package Risk Levels": {
"main": [
[
{
"node": "Generate Developer Summary",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleDriveOAuth2ApiopenAiApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow monitors a Google Drive folder for newly uploaded APK files, automatically downloads them, triggers a MobSF static analysis scan, processes the output to detect unused or risky libraries and sends a developer-friendly summary directly to Slack. It is ideal for…
Source: https://n8n.io/workflows/14543/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow automatically turns any audio file uploaded to Google Drive into a complete podcast episode. It handles transcription, content generation, blog drafting, social copy creation, thumbnail
This template is ideal for photographers, graphic designers, and creative professionals who manage large volumes of visual assets. It is also perfect for Digital Asset Managers looking for a customiza
The Problem That it Solves
Content creators, YouTubers, and social media managers who want to repurpose long form videos into short clips without doing it manually. Works on self hosted n8n instances.
This workflow is an AI-powered lighting and look development pipeline designed for VFX production. It transforms a single lighting brief into multiple high-quality cinematic lighting references using