diff --git a/README.md b/README.md index 3ec864d..11c62bf 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,19 @@ The results of the audit are written to an SQLite database, which is opened auto the end of the run. The results are in the table named "audit_result". The table has a column named "has_vulnerability", with checkmarks in the rows that are most likely to be genuine vulnerabilities. +## Mobile application support + +The audit workflow recognizes Android, iOS, and cross-platform mobile applications alongside web applications and libraries. +Mobile entry-point collection records exported Android components, deep links, URL schemes, Universal Links, app extensions, +and WebView JavaScript bridges, including their permissions, export status, and input filters. + +The [audit runner](scripts/audit/run_audit.sh) collects this metadata before classification, which applies mobile-specific +guidance to components marked as mobile applications. The existing `--advisory` and `-m` options also apply to mobile runs. +Both mobile entry-point taskflows use the container-backed source-access toolbox and require Docker. + +Mobile metadata is stored in the `mobile_entry_point` table. Existing context databases automatically gain the nullable +`application.is_mobile_app` column without deleting existing results. + ## Running with docker script We recommend running taskflows in a sandboxed environment. [GitHub Codespaces](https://github.com/features/codespaces) are convenient, or if you prefer you can use the script [`run_seclab_agent.sh`](scripts/run_seclab_agent.sh) to run a docker container of the `seclab-taskflow-agent` as outlined [here](https://github.com/GitHubSecurityLab/seclab-taskflow-agent/tree/main?tab=readme-ov-file#deploying-from-docker). Note that this script needs to be run from the main directory of the repo, and the `.env` file with the environment variables for the custom MCP servers to store data needs to be in the same directory. diff --git a/scripts/audit/run_audit.sh b/scripts/audit/run_audit.sh index 14b3bb8..728870a 100755 --- a/scripts/audit/run_audit.sh +++ b/scripts/audit/run_audit.sh @@ -33,6 +33,7 @@ fi python -m seclab_taskflow_agent $MODEL_CONFIG_FLAG -t seclab_taskflows.taskflows.audit.fetch_source_code -g repo="$1" python -m seclab_taskflow_agent $MODEL_CONFIG_FLAG -t seclab_taskflows.taskflows.audit.identify_applications -g repo="$1" python -m seclab_taskflow_agent $MODEL_CONFIG_FLAG -t seclab_taskflows.taskflows.audit.gather_web_entry_point_info -g repo="$1" +python -m seclab_taskflow_agent $MODEL_CONFIG_FLAG -t seclab_taskflows.taskflows.audit.gather_mobile_entry_point_info -g repo="$1" python -m seclab_taskflow_agent $MODEL_CONFIG_FLAG -t seclab_taskflows.taskflows.audit.gather_security_entry_point_info -g repo="$1" if [ "$USE_ADVISORY" = true ]; then diff --git a/src/seclab_taskflows/mcp_servers/repo_context.py b/src/seclab_taskflows/mcp_servers/repo_context.py index 96a4a3f..fb1a7c4 100644 --- a/src/seclab_taskflows/mcp_servers/repo_context.py +++ b/src/seclab_taskflows/mcp_servers/repo_context.py @@ -9,12 +9,12 @@ import os import yaml from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped -from sqlalchemy import create_engine +from sqlalchemy import create_engine, inspect as sa_inspect, text from sqlalchemy.orm import Session from pathlib import Path from seclab_taskflow_agent.path_utils import mcp_data_dir, log_file_name -from .repo_context_models import Application, EntryPoint, UserAction, WebEntryPoint, ApplicationIssue, AuditResult, SecurityEntryPoint, Base +from .repo_context_models import Application, EntryPoint, UserAction, WebEntryPoint, MobileEntryPoint, ApplicationIssue, AuditResult, SecurityEntryPoint, Base from .repo_context_models import LowSeverityAuditResult, AuditResultEvidence, ExternalDependency from .utils import process_repo @@ -35,6 +35,7 @@ def app_to_dict(result): "notes": result.notes, "is_app": bool(result.is_app), "is_library": bool(result.is_library), + "is_mobile_app": bool(result.is_mobile_app), } @@ -86,6 +87,21 @@ def security_entry_point_to_dict(sep): "notes": sep.notes, } +def mobile_entry_point_to_dict(mep): + return { + "id": mep.id, + "entry_point_id": mep.entry_point_id, + "entry_type": mep.entry_type, + "scheme_or_action": mep.scheme_or_action, + "exported": mep.exported, + "permissions": mep.permissions, + "data_filter": mep.data_filter, + "component": mep.component, + "repo": mep.repo.lower(), + "notes": mep.notes, + } + + def audit_result_to_dict(res): return { "id": res.id, @@ -115,6 +131,7 @@ def __init__(self, memcache_state_dir: str): EntryPoint.__table__, UserAction.__table__, WebEntryPoint.__table__, + MobileEntryPoint.__table__, ApplicationIssue.__table__, AuditResult.__table__, LowSeverityAuditResult.__table__, @@ -123,8 +140,24 @@ def __init__(self, memcache_state_dir: str): ExternalDependency.__table__, ], ) + self._migrate_schema() - def store_new_application(self, repo, location, is_app, is_library, notes): + def _migrate_schema(self): + """Add any missing columns to existing tables (lightweight migration for SQLite).""" + inspector = sa_inspect(self.engine) + migrations = [ + ("application", "is_mobile_app", "BOOLEAN"), + ] + for table_name, column_name, column_type in migrations: + if not inspector.has_table(table_name): + continue + existing_columns = {col["name"] for col in inspector.get_columns(table_name)} + if column_name not in existing_columns: + logging.info(f"Migrating: adding column {column_name} to {table_name}") + with self.engine.begin() as conn: + conn.execute(text(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}")) + + def store_new_application(self, repo, location, is_app, is_library, notes, is_mobile_app=None): with Session(self.engine) as session: existing = session.query(Application).filter_by(repo=repo, location=location).first() if existing: @@ -132,10 +165,13 @@ def store_new_application(self, repo, location, is_app, is_library, notes): existing.is_app = is_app if is_library is not None: existing.is_library = is_library + if is_mobile_app is not None: + existing.is_mobile_app = is_mobile_app existing.notes += notes else: new_application = Application( - repo=repo, location=location, is_app=is_app, is_library=is_library, notes=notes + repo=repo, location=location, is_app=is_app, is_library=is_library, + is_mobile_app=is_mobile_app, notes=notes ) session.add(new_application) session.commit() @@ -267,6 +303,53 @@ def store_new_security_entry_point(self, repo, entry_point_id, type, component, session.commit() return f"Updated or added security entry point for entry_point_id {entry_point_id} in {repo}." + def store_new_mobile_entry_point( + self, repo, entry_point_id, entry_type, scheme_or_action, exported, permissions, data_filter, component, notes, update=False + ): + with Session(self.engine) as session: + existing = session.query(MobileEntryPoint).filter_by(repo=repo, entry_point_id=entry_point_id).first() + if existing: + existing.notes += notes + if entry_type: + existing.entry_type = entry_type + if scheme_or_action: + existing.scheme_or_action = scheme_or_action + if exported is not None: + existing.exported = exported + if permissions: + existing.permissions = permissions + if data_filter: + existing.data_filter = data_filter + if component is not None: + existing.component = component + else: + if update: + return f"No mobile entry point exists at repo {repo} with entry_point_id {entry_point_id}." + new_mobile_entry_point = MobileEntryPoint( + repo=repo, + entry_point_id=entry_point_id, + entry_type=entry_type, + scheme_or_action=scheme_or_action, + exported=exported, + permissions=permissions, + data_filter=data_filter, + component=component, + notes=notes, + ) + session.add(new_mobile_entry_point) + session.commit() + return f"Updated or added mobile entry point for entry_point_id {entry_point_id} in {repo}." + + def get_mobile_entries(self, repo, component_id): + with Session(self.engine) as session: + results = session.query(MobileEntryPoint).filter_by(repo=repo, component=component_id).all() + return [mobile_entry_point_to_dict(r) for r in results] + + def get_mobile_entries_for_repo(self, repo): + with Session(self.engine) as session: + results = session.query(MobileEntryPoint).filter_by(repo=repo).all() + return [mobile_entry_point_to_dict(r) for r in results] + def store_new_user_action(self, repo, app_id, file, line, notes, update=False): with Session(self.engine) as session: existing = session.query(UserAction).filter_by(repo=repo, file=file, line=line).first() @@ -517,6 +600,7 @@ def clear_repo(self, repo): session.query(UserAction).filter_by(repo=repo).delete() session.query(ApplicationIssue).filter_by(repo=repo).delete() session.query(WebEntryPoint).filter_by(repo=repo).delete() + session.query(MobileEntryPoint).filter_by(repo=repo).delete() session.query(AuditResult).filter_by(repo=repo).delete() session.query(LowSeverityAuditResult).filter_by(repo=repo).delete() session.query(SecurityEntryPoint).filter_by(repo=repo).delete() @@ -544,11 +628,14 @@ def store_new_component( is_app: bool = Field(description="Is this an application", default=None), is_library: bool = Field(description="Is this a library", default=None), notes: str = Field(description="The notes taken for this component", default=""), + is_mobile_app: bool = Field(description="Is this a mobile application (e.g. Android, iOS)", default=None), ): """ Stores a new component in the database. """ - return backend.store_new_application(process_repo(owner, repo), location, is_app, is_library, notes) + return backend.store_new_application( + process_repo(owner, repo), location, is_app, is_library, notes, is_mobile_app=is_mobile_app + ) @mcp.tool() @@ -849,6 +936,62 @@ def get_security_entry_points_for_repo( repo = process_repo(owner, repo) return json.dumps(backend.get_security_entries_for_repo(repo)) +@mcp.tool() +def store_new_mobile_entry_point( + owner: str = Field(description="The owner of the GitHub repository"), + repo: str = Field(description="The name of the GitHub repository"), + entry_point_id: int = Field(description="The ID of the entry point this mobile entry point refers to"), + location: str = Field(description="The directory of the component; required when component is not supplied", default=""), + entry_type: str = Field(description="Type of mobile entry point: deep_link, intent, url_scheme, content_provider, broadcast_receiver, js_bridge, app_extension, universal_link, etc.", default=""), + scheme_or_action: str = Field(description="URL scheme (e.g. myapp://) or intent action (e.g. android.intent.action.VIEW)", default=""), + exported: bool = Field(description="Whether the Android component is exported (accessible to other apps)", default=None), + permissions: str = Field(description="Required permissions to access this entry point, if any", default=""), + data_filter: str = Field(description="Intent data filter, URL path pattern, host, etc.", default=""), + component: int = Field(description="Component identifier", default=0), + notes: str = Field(description="Notes for this mobile entry point", default=""), +): + """ + Stores a new mobile entry point in a component to the database. A mobile entry point extends a regular entry point + with mobile-specific properties like entry type, URL scheme/intent action, export status, permissions, and data filters. + """ + repo = process_repo(owner, repo) + if component == 0: + if not location: + return "Error: Provide component or location for this mobile entry point." + app = backend.get_app(repo, location) + if not app: + return f"Error: No component exists in repo: {repo} and location {location}" + component = app.id + return backend.store_new_mobile_entry_point( + repo, entry_point_id, entry_type, scheme_or_action, exported, permissions, data_filter, component, notes + ) + + +@mcp.tool() +def get_mobile_entry_points_component( + owner: str = Field(description="The owner of the GitHub repository"), + repo: str = Field(description="The name of the GitHub repository"), + component_id: int = Field(description="The ID of the component"), +): + """ + Get all mobile entry points for a component + """ + repo = process_repo(owner, repo) + return json.dumps(backend.get_mobile_entries(repo, component_id)) + + +@mcp.tool() +def get_mobile_entry_points_for_repo( + owner: str = Field(description="The owner of the GitHub repository"), + repo: str = Field(description="The name of the GitHub repository"), +): + """ + Get all mobile entry points of a repo + """ + repo = process_repo(owner, repo) + return json.dumps(backend.get_mobile_entries_for_repo(repo)) + + @mcp.tool() def get_user_actions( owner: str = Field(description="The owner of the GitHub repository"), diff --git a/src/seclab_taskflows/mcp_servers/repo_context_models.py b/src/seclab_taskflows/mcp_servers/repo_context_models.py index b2e0047..710875d 100644 --- a/src/seclab_taskflows/mcp_servers/repo_context_models.py +++ b/src/seclab_taskflows/mcp_servers/repo_context_models.py @@ -19,12 +19,13 @@ class Application(Base): notes: Mapped[str] = mapped_column(Text) is_app: Mapped[bool] = mapped_column(nullable=True) is_library: Mapped[bool] = mapped_column(nullable=True) + is_mobile_app: Mapped[bool] = mapped_column(nullable=True) def __repr__(self): return ( f"" + f"location={self.location}, is_app={self.is_app}, is_library={self.is_library}, " + f"is_mobile_app={self.is_mobile_app}, notes={self.notes}>" ) @@ -148,6 +149,31 @@ def __repr__(self): f"untrusted_input={self.untrusted_input}, notes={self.notes}, repo={self.repo})>" ) +class MobileEntryPoint(Base): + """An entry point specific to mobile applications (e.g. deep links, intents, URL schemes).""" + __tablename__ = "mobile_entry_point" + + id: Mapped[int] = mapped_column(primary_key=True) + entry_point_id = Column(Integer, ForeignKey("entry_point.id", ondelete="CASCADE")) + entry_type: Mapped[str] # deep_link, intent, url_scheme, content_provider, broadcast_receiver, js_bridge, etc. + scheme_or_action: Mapped[str] # URL scheme (myapp://) or intent action (android.intent.action.VIEW) + exported: Mapped[bool] = mapped_column(nullable=True) # whether the component is exported (Android) + permissions: Mapped[str] # required permissions, if any + data_filter: Mapped[str] # intent data filter, URL path pattern, etc. + component: Mapped[int] # component id + notes: Mapped[str] = mapped_column(Text) + repo: Mapped[str] + + def __repr__(self): + return ( + f"" + ) + + class UserAction(Base): __tablename__ = "user_action" diff --git a/src/seclab_taskflows/taskflows/audit/classify_application.yaml b/src/seclab_taskflows/taskflows/audit/classify_application.yaml index 092f58c..3f17325 100644 --- a/src/seclab_taskflows/taskflows/audit/classify_application.yaml +++ b/src/seclab_taskflows/taskflows/audit/classify_application.yaml @@ -29,6 +29,15 @@ taskflow: An authentication/authorization component may be susceptible to IDOR, bypasses etc. Components that grant user access to data may require access control or authentication. + For mobile applications, consider risks such as: + - Deep link / URL scheme hijacking or mishandling that triggers sensitive actions without validation + - Exported Android components (Activities, Services, BroadcastReceivers, ContentProviders) accessible + to malicious apps via crafted intents + - WebViews loading untrusted content with JavaScript bridges that expose native functionality + - Insecure data storage (tokens, credentials, PII in world-readable or unencrypted locations) + - Content Provider exposure allowing other apps to read or modify sensitive data + - Path traversal via file handling from untrusted sources (downloads, shared content, zip archives) + We're looking for more concrete and serious security issues that affects system integrity or lead to information leak, so please do not include issues like brute force, Dos, log injection etc. diff --git a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml index 374a625..a9aa247 100644 --- a/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml +++ b/src/seclab_taskflows/taskflows/audit/classify_application_local.yaml @@ -34,8 +34,8 @@ taskflow: {{ result.notes }} - Fetch the entry points and web entry points of the component, then the user actions of this component. - Based on the entry points, web entry points, components, user actions and README.md and if available, SECURITY.md in the {{ globals.repo }}, + Fetch the entry points, web entry points and mobile entry points of the component, then the user actions of this component. + Based on the entry points, web entry points, mobile entry points, components, user actions and README.md and if available, SECURITY.md in the {{ globals.repo }}, can you tell me what type of application this repo is and what kind of security boundary it has. Based on this, determine whether the component is likely to have security problems. @@ -49,6 +49,155 @@ taskflow: {% include 'seclab_taskflows.prompts.audit.classify_application_component' %} {% endif %} + {% if result.is_mobile_app %} + Mobile review priorities + + Use the recorded mobile entry points as a starting inventory, not an exhaustive boundary for + this review. Perform a focused source search for each category below, then read the relevant + code and configuration before proposing issues. Keep this pass at risk classification rather + than full exploit verification. Distinguish another app, a web page, a remote service, and the + device user; they need not have the same privileges. + + Cover incoming data, outgoing sensitive data, and externally supplied code or resources + separately. A trusted UI action or background task can consume an untrusted resource or send + private data to an unintended recipient. Inspect those paths even when their trigger is not + an exported component or a previously stored entry point. Do not equate a trusted trigger + with trusted downstream data, code, or recipients. + + Read source with file-local line numbers that restart for each file. Do not cite cumulative + line numbers from concatenated files. If source is inaccessible, report that the review is + blocked instead of inferring the implementation from project familiarity or documentation. + + - Caller authorization: For Android activities, services, receivers, and providers, or iOS + extensions, describe who should be able to request each operation. Distinguish platform + permissions from account-level access rules. Relevant concerns include missing authorization + and confused-deputy behavior when the app acts on a caller's behalf. + Include Android permission declarations and protection levels, runtime receiver permissions, + and iOS extension or app-group entitlements in the expected access policy. + + - Navigation and delegation: For deep links, App Links, Universal Links, login callbacks, + intents, and PendingIntents, record the intended destination and action. Describe which + decisions belong to the user or authenticated session rather than the incoming request. + Relevant concerns include intent redirection and incorrect binding of authentication callbacks. + Include URL scheme/host/path validation, callback session binding, PendingIntent mutability, + and restrictions on delegated actions. Link association does not replace authorization. + Locate getParcelableExtra, Intent.parseUri, startActivity, startService, and setResult calls. + For supplied intents, identify which component, action, data, ClipData, and permission flags + the sender can choose and which restrictions the app is expected to enforce. Treat navigation + to an activity and granting access to a content URI as different mechanisms. + + - Message and resource sharing: For broadcasts, shared documents, content URIs, and app groups, + describe the intended senders, recipients, resources, and duration of access. Relevant concerns + include broadcast spoofing, unintended disclosure through implicit messaging, and overly broad + resource grants. Do not assume an exported interface is unintended merely because it is public. + Explicitly consider FileProvider directory scope, read/write URI grants, recipient selection, + and the expected authentication of message senders. + Search registerReceiver and sendBroadcast separately: receiving forged messages and exposing + outgoing secrets are different concerns. Inspect sensitive extras in outgoing broadcasts, + implicit activity/service intents, and activity results, including sends triggered by buttons + or successful login. Look for explicit destinations and receiver permissions, not just the + receiving component's export status. + Follow manifest FileProvider metadata into its paths XML. Review root-path and broad + external-path/files-path mappings alongside getUriForFile, grantUriPermission, ClipData, + and FLAG_GRANT_READ_URI_PERMISSION / FLAG_GRANT_WRITE_URI_PERMISSION use. Record which + resource and recipient a grant can cover; broad paths alone do not prove a usable access path. + + - Web content in the app: For WebView or WKWebView components, distinguish the web document's + origin from the app's native privileges. Record which content may navigate, run scripts, or + invoke a native bridge. Relevant concerns include cross-site scripting and missing origin + restrictions on privileged bridge operations. + Include HTML injection, same-origin isolation, file-origin access, mixed content, navigation + restrictions, and WebView debugging. Also consider cookie and session isolation and whether + camera, microphone, and geolocation access decisions are scoped to the requesting origin. + Locate loadUrl, loadDataWithBaseURL, evaluateJavascript, addJavascriptInterface, and + WKScriptMessageHandler uses. For each relevant call, distinguish control of the loaded URL, + document content, base origin, and native bridge methods. Inspect allowFileAccess, + allowFileAccessFromFileURLs, and allowUniversalAccessFromFileURLs with the document's actual + origin. File-origin settings do not by themselves let a remote HTTPS page read local files. + + - Imported data and parsing: For shared files, downloaded documents, archives, and media, + describe the accepted formats, intended output locations, and downstream consumers. Relevant + concerns include path traversal, unsafe parsing, and treating external data as executable + instructions. Include native-code handling only where the component actually uses it. + Name applicable review concerns explicitly: SQL injection, predicate injection, XPath injection, + XML external entities, unsafe XSLT processing, unsafe deserialization, and OS command injection. + Record whether input should remain data rather than becoming a query, command, or template. + Include file read/write/delete boundaries, archive extraction, and symbolic-link handling. + Locate rawQuery/execSQL, NSPredicate format construction, XML parser configuration, process + execution, and archive/file-copy operations where present. Follow URI-to-path conversions and + canonicalization rather than assuming that a file URI and an absolute filesystem path are + interchangeable. Record platform/storage prerequisites for proposed disclosure or overwrite. + + - Native runtime and code loading: For JNI and other native dependencies, note whether native + memory safety warrants review, including bounds checking, integer overflow, and object lifetime. + For dynamic code loading, plugins, and embedded interpreters, record the expected source and + integrity protection of executable content. Keep code provenance distinct from ordinary data + validation; the presence of a native library or interpreter alone is not a vulnerability. + Search createPackageContext with CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY, installed + package enumeration, classLoader/loadClass, DexClassLoader, System.load, and dlopen where + applicable. Determine whether package identity, signing certificates, and code integrity are + checked before execution. A package-name prefix or matching class name is not a signer check. + Include these paths in UI callbacks and error/fallback branches, not only IPC handlers. + + - Service connections: For backend requests and authentication handoffs, describe the expected + service identity and which credentials or account data belong with that service. Relevant + concerns include incorrect TLS peer verification and sending session information to an + unintended destination. Distinguish app-side responsibilities from server-side authorization. + Include cleartext traffic, hostname validation, network-security or ATS exceptions, credential + handling across redirects, HTTP request construction, and HTTP cache integrity. For embedded + HTTP or socket listeners, note local-server authentication and network binding scope; do not + assume that a service is private simply because it runs on the device. + Inspect custom TrustManager/checkServerTrusted, HostnameVerifier, onReceivedSslError handlers, + and ATS configuration where present. Distinguish accepting a supplied URL from failing to + authenticate a service that the app is supposed to trust; record the relevant trust boundary. + + - Data lifetime and device access: For stored sessions, keys, logs, backups, and shared storage, + record who should retain access and for how long, including account changes and logout. + Relevant concerns include sensitive-data exposure and inappropriate key management. For + camera, microphone, or location features, distinguish OS permission from user consent to a + particular operation; a granted permission alone does not establish the intended behavior. + Include plaintext credential storage, sensitive logging, clipboard exposure, backup policy, + cross-account cache separation, Android file permissions, iOS file-protection classes, and + Keychain accessibility and access groups. Relate each concern to the data's sensitivity and + the documented threat model, rather than assuming physical access or a compromised device. + Trace credentials from acquisition to persistence and any logs, notifications, clipboard, + provider responses, or messages that expose them. Do not treat private plaintext storage or + debug logging alone as cross-app access without identifying the required access conditions. + + - Cryptographic assets: Where the component uses cryptography, consider hardcoded keys, + weak randomness, inappropriate key derivation, nonce or IV misuse, and missing integrity + protection. Record intended key storage, exportability, rotation, and deletion requirements. + Include bundled private signing-key material; distinguish private secrets from public + certificates, identifiers, and values intentionally designed to be public. + Distinguish Random from SecureRandom in security-sensitive generation, and inspect key/nonce + construction at Cipher or platform cryptographic API calls rather than flagging API names alone. + + - Release and platform policy: Separate release-build debugging, diagnostic interfaces, test + credentials, and developer-only settings from production behavior. Record platform-version + behavior using the actual Android minimum/target SDK or iOS deployment version, including + relevant differences between manifest declarations, entitlements, and runtime controls. + Do not apply historical platform weaknesses to unsupported versions or treat a deprecated API + as proof of a vulnerability. Build settings are relevant only in the configuration being reviewed. + + These are review priorities, not verified vulnerabilities or a guarantee of complete coverage. + For each applicable area, record the entry point, affected resource, expected restriction, and + supporting repository file references. State which protections are documented and which remain + unknown; lack of evidence is not evidence of a missing check. Skip irrelevant categories and + note platform or SDK assumptions instead of applying Android-specific rules to iOS code. + Other review priorities may be noted when supported by the component's documented purpose. + Store relevant risk hypotheses as component issues with the affected operation, external + influence or disclosure destination, expected restriction, and prerequisites in the notes. + Merge candidates with the same root cause and source location instead of duplicating an issue + under several category names. Do not mark a hypothesis as verified at this stage. + Keep this stage to defensive source review; do not produce exploit payloads, reproduction + instructions, or interact with a running target. + + Background references for the review categories: + - Android security guidance: https://developer.android.com/privacy-and-security/security-tips + - Apple Platform Security: https://support.apple.com/guide/security/welcome/web + - OWASP MASVS: https://mas.owasp.org/MASVS/ + {% endif %} + It is not your task to audit the security measures, but rather just to identify the risks and suggest some issues that is worth auditing. diff --git a/src/seclab_taskflows/taskflows/audit/gather_mobile_entry_point_info.yaml b/src/seclab_taskflows/taskflows/audit/gather_mobile_entry_point_info.yaml new file mode 100644 index 0000000..c7b833e --- /dev/null +++ b/src/seclab_taskflows/taskflows/audit/gather_mobile_entry_point_info.yaml @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: GitHub, Inc. +# SPDX-License-Identifier: MIT + +seclab-taskflow-agent: + filetype: taskflow + version: "1.0" +model_config: seclab_taskflows.configs.model_config + +globals: + repo: +# Taskflow to analyze existing entry points and enrich them with mobile-specific information +taskflow: + - task: + model: code_analysis + must_complete: false + headless: true + max_steps: 100 + agents: + - seclab_taskflows.personalities.web_application_security_expert + user_prompt: | + For the repo {{ globals.repo }} iterate over all entry points and check if they represent + a mobile application entry point. Mobile entry points include: + + ## Android + - Exported Activities, Services, BroadcastReceivers, and ContentProviders declared in AndroidManifest.xml + - Intent filters that make components reachable via implicit intents + - Deep links and App Links (intent filters with ACTION_VIEW, BROWSABLE category, and data URI schemes) + - JavaScript bridges exposed in WebViews (addJavascriptInterface) + + ## iOS + - Custom URL schemes registered in Info.plist (CFBundleURLSchemes) + - Universal Links (Associated Domains entitlement and apple-app-site-association) + - App Extensions (share extensions, action extensions, etc.) + - JavaScript bridges in WKWebView (WKScriptMessageHandler) + + For each entry point, check the noted `file` and its line using the container shell for source access. Then determine: + - The entry type (deep_link, intent, url_scheme, content_provider, broadcast_receiver, js_bridge, + universal_link, app_extension, etc.) + - The URL scheme or intent action that triggers it + - Whether the component is exported / accessible to other apps + - Any permissions required to access this entry point + - Any data filters (host, path patterns, MIME types) that restrict what data is accepted + + For each entry point identified as a mobile entry point, create a new mobile entry point using + the information gathered. + toolboxes: + - seclab_taskflows.toolboxes.repo_context + - seclab_taskflows.toolboxes.container_shell_source_access diff --git a/src/seclab_taskflows/taskflows/audit/gather_mobile_entry_point_info_local.yaml b/src/seclab_taskflows/taskflows/audit/gather_mobile_entry_point_info_local.yaml new file mode 100644 index 0000000..e592968 --- /dev/null +++ b/src/seclab_taskflows/taskflows/audit/gather_mobile_entry_point_info_local.yaml @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: GitHub, Inc. +# SPDX-License-Identifier: MIT + +seclab-taskflow-agent: + filetype: taskflow + version: "1.0" +model_config: seclab_taskflows.configs.model_config + +globals: + repo: +# Taskflow to analyze existing entry points and enrich them with mobile-specific information +taskflow: + - task: + must_complete: true + headless: true + model: general_tasks + exclude_from_context: true + agents: + - seclab_taskflow_agent.personalities.assistant + user_prompt: | + Fetch the components of the repo {{ globals.repo }}. + toolboxes: + - seclab_taskflows.toolboxes.repo_context + - task: + model: code_analysis + must_complete: true + repeat_prompt: true + async: true + max_steps: 100 + agents: + - seclab_taskflows.personalities.web_application_security_expert + user_prompt: | + The component has component_id {{ result.app_id }} and location {{ result.location }} in the repo {{ result.repo }}. + Fetch the entry points of the component and for each entry point, check if this entry point is a mobile + application entry point. + + Mobile entry points include: + + ## Android + - Exported Activities, Services, BroadcastReceivers, and ContentProviders declared in AndroidManifest.xml + - Intent filters that make components reachable via implicit intents + - Deep links and App Links (intent filters with ACTION_VIEW, BROWSABLE category, and data URI schemes) + - JavaScript bridges exposed in WebViews (addJavascriptInterface) + + ## iOS + - Custom URL schemes registered in Info.plist (CFBundleURLSchemes) + - Universal Links (Associated Domains entitlement and apple-app-site-association) + - App Extensions (share extensions, action extensions, etc.) + - JavaScript bridges in WKWebView (WKScriptMessageHandler) + + For each entry point, check the noted `file` and its line using the container shell for source access. Then determine: + - The entry type (deep_link, intent, url_scheme, content_provider, broadcast_receiver, js_bridge, + universal_link, app_extension, etc.) + - The URL scheme or intent action that triggers it + - Whether the component is exported / accessible to other apps + - Any permissions required to access this entry point + - Any data filters (host, path patterns, MIME types) that restrict what data is accepted + + For each entry point identified as a mobile entry point, create a new mobile entry point using + the information gathered. + toolboxes: + - seclab_taskflows.toolboxes.repo_context + - seclab_taskflows.toolboxes.container_shell_source_access diff --git a/src/seclab_taskflows/taskflows/audit/identify_applications.yaml b/src/seclab_taskflows/taskflows/audit/identify_applications.yaml index f291fe5..67f9759 100644 --- a/src/seclab_taskflows/taskflows/audit/identify_applications.yaml +++ b/src/seclab_taskflows/taskflows/audit/identify_applications.yaml @@ -31,9 +31,13 @@ taskflow: - seclab_taskflows.personalities.web_application_security_expert user_prompt: | Inspect {{ globals.repo }} and determine its function. Determine whether the repo is a framework library, - or a web application etc. For repos that may contain multiple applications, determine the purpose of each + web application, mobile application etc. For repos that may contain multiple applications, determine the purpose of each application and the directories of each application. Repos can also be libraries but contain actual applications, for example, a web server and admin UI etc. that are not for testing or examples. + + Mobile applications include Android apps (Java/Kotlin with Android SDK, Gradle-based), iOS apps + (Swift/Objective-C with Xcode), React Native, Flutter, Xamarin, or other cross-platform mobile frameworks. + Mark these as mobile applications when storing the component. Apart from source code, it is also useful to look at the README.md and documentations in the repo. @@ -81,13 +85,32 @@ taskflow: {{ result.notes }} Analyze the entry points of this application. First check whether this component is a library or - an application. + an application. {% if result.is_library %} {% include 'seclab_taskflows.prompts.audit.entry_point_library' %} {% else %} {% include 'seclab_taskflows.prompts.audit.entry_point_application' %} {% endif %} + {% if result.is_mobile_app %} + ## Mobile Application + + For mobile application components, consider the following sources of untrusted input: + + - Deep links and custom URL schemes that can be invoked by other apps or web pages + - Intent data received from other applications (Android) or URL schemes / Universal Links (iOS) + - Data received from external services via network requests (API responses, push notifications) + - Content shared from other apps via share intents or extensions + - Data read from external storage, clipboard, or other IPC mechanisms + - User input from UI elements (text fields, file pickers, camera/gallery input) + + Determine trust in stored values (SharedPreferences, UserDefaults, local databases) from their + origin, who can influence them, and validation and integrity protections appropriate to their use. + Persisting externally controlled input in private storage does not by itself make it trusted. + Identify the original trust boundary and relevant persisted-input paths rather than treating + every storage read or UI action as a new attacker-controlled entry point. + {% endif %} + Only identify the untrusted inputs and entry points, do not analyze the security risks of the component or the entry point. @@ -159,6 +182,22 @@ taskflow: {% include 'seclab_taskflows.prompts.audit.user_action_application' %} {% endif %} + {% if result.is_mobile_app %} + ## Mobile Application + + For mobile application components, consider the following: + + What actions can an external entity (another app, a web page, a remote server) trigger in this + mobile application? Consider: + - Actions triggered via deep links or custom URL schemes + - Actions triggered via intents from other apps (Android) or app extensions (iOS) + - Data that the app processes from untrusted network responses + - Actions triggered by push notifications or shared content + + Focus on what the mobile app is designed to do with external input, not on security bypasses. + State clearly what an external entity is allowed to trigger and what restrictions exist. + {% endif %} + Store an user_action entry stating precising the file and line number where the allowed action is performed, and provide notes about the action. diff --git a/src/seclab_taskflows/taskflows/audit/identify_applications_local.yaml b/src/seclab_taskflows/taskflows/audit/identify_applications_local.yaml index e9dadc3..377f562 100644 --- a/src/seclab_taskflows/taskflows/audit/identify_applications_local.yaml +++ b/src/seclab_taskflows/taskflows/audit/identify_applications_local.yaml @@ -34,10 +34,14 @@ taskflow: - seclab_taskflows.personalities.web_application_security_expert user_prompt: | Inspect {{ globals.repo }} and determine its function. Determine whether the repo is a framework library, - or a web application etc. For repos that may contain multiple applications, determine the purpose of each + or a web application, mobile application etc. For repos that may contain multiple applications, determine the purpose of each application and the directories of each application. Repos can also be libraries but contain actual applications, for example, a web server and admin UI etc. that are not for testing or examples. + Mobile applications include Android apps (Java/Kotlin with Android SDK, Gradle-based), iOS apps + (Swift/Objective-C with Xcode), React Native, Flutter, Xamarin, or other cross-platform mobile frameworks. + Mark these as mobile applications when storing the component. + Be granular when identifying components. Even if the repo is a library, do not store it as a single component. Group directories together according to functionalities as separate components. Try to identify standalone web application or middleware and list each one as an individual component. Even in library repo, there are @@ -91,6 +95,7 @@ taskflow: is_application ? {{ result.is_app }} is_library ? {{ result.is_library }} + is_mobile_app ? {{ result.is_mobile_app }} ## Application @@ -105,6 +110,25 @@ taskflow: untrusted input. For example, a parser may take a user argument that points to a file, and in this case, the file is still considered untrusted even though it is supplied via user argument. + {% if result.is_mobile_app %} + ## Mobile Application + + For mobile application components, consider the following sources of untrusted input: + + - Deep links and custom URL schemes that can be invoked by other apps or web pages + - Intent data received from other applications (Android) or URL schemes / Universal Links (iOS) + - Data received from external services via network requests (API responses, push notifications) + - Content shared from other apps via share intents or extensions + - Data read from external storage, clipboard, or other IPC mechanisms + - User input from UI elements (text fields, file pickers, camera/gallery input) + + Determine trust in stored values (SharedPreferences, UserDefaults, local databases) from their + origin, who can influence them, and validation and integrity protections appropriate to their use. + Persisting externally controlled input in private storage does not by itself make it trusted. + Identify the original trust boundary and relevant persisted-input paths rather than treating + every storage read or UI action as a new attacker-controlled entry point. + {% endif %} + ## Library For library code, identify the functions that are exposed to a library user. Consider the library user @@ -188,6 +212,7 @@ taskflow: is_application ? {{ result.is_app }} is_library ? {{ result.is_library }} + is_mobile_app ? {{ result.is_mobile_app }} ## Application @@ -200,6 +225,22 @@ taskflow: You should also state clearly the actions and privileges that a user can have when using the application. + {% if result.is_mobile_app %} + ## Mobile Application + + For mobile application components, consider the following: + + What actions can an external entity (another app, a web page, a remote server) trigger in this + mobile application? Consider: + - Actions triggered via deep links or custom URL schemes + - Actions triggered via intents from other apps (Android) or app extensions (iOS) + - Data that the app processes from untrusted network responses + - Actions triggered by push notifications or shared content + + Focus on what the mobile app is designed to do with external input, not on security bypasses. + State clearly what an external entity is allowed to trigger and what restrictions exist. + {% endif %} + ## Library For library component, do the following instead: