Custom Properties
September 24, 2026 · View on GitHub
This page documents the custom properties that the AppThreat tooling adds to a CycloneDX BOM. blint is the producer of the BOM and emits most of these properties. atom-tools enriches the same BOM with reachability information derived from atom slices, and chen supplies the semantic tags that atom-tools turns into services and service properties. The goal of this document is to give a single technical reference for every non standard field, the object it appears on, the value encoding, when it is emitted, and how it can be used in analysis and policy.
These properties are specific to the AppThreat tools. They are not part of the CycloneDX core specification. They are intended to enrich analysis and policy decisions, and consumers should treat them as advisory enrichment rather than authoritative inventory.
Scope
The properties described here are written by three tools that share one BOM.
blint parses the binary, Android, and iOS/macOS (.ipa) artifacts and writes the component inventory together with the internal: and internal. properties. When deep mode is enabled it also parses the dex classes (Android) and the linked dylibs of each Mach-O (iOS), which is what makes service detection and behavioural review possible. When disassembly is enabled it additionally writes callgraph sidecars next to the BOM: a Dalvik callgraph for android apps and a native Mach-O callgraph per embedded binary for iOS/macOS apps.
atom-tools runs blint and atom, then merges the reachability evidence from atom into the BOM. It promotes the services that the application actually reaches into the services array and attaches reachability counts to them.
chen is the code property graph library used by atom. Its tagger passes attach semantic tags such as pii, tracker, and service-egress to the flows in a reachable slice. atom-tools reads those tags and converts them into CycloneDX services and the service level properties described later in this document.
How to read these properties
CycloneDX custom properties are name and value pairs, so every value is serialized as a string even when it represents a boolean, a number, or a list. Consumers should assume string values and coerce them explicitly before comparison.
The value encodings used by these properties are summarized below.
| Shape | Encoding | Examples |
|---|---|---|
| Boolean | the strings true or false | internal:onDeviceAi |
| Number like | a decimal string | internal:versionCode, internal:reachableFlows |
| Single value | a plain string | internal:minSdkVersion, internal:mainActivity |
| Newline separated list | values joined by a newline | internal.appPermissions, internal.appFeatures |
| Delimited list | values joined by the blint symbol delimiter ~~ | internal:functions, internal:classes |
| Compound value | fields joined by the pipe character | internal:behaviour:<ID> |
Two namespaces are in use. The dotted namespace such as internal.appPermissions is the older form retained for the manifest derived application metadata. The colon namespace such as internal:minSdkVersion is the preferred form for everything added more recently. New properties should use the colon namespace.
Application component properties
These properties appear on the parent application component, which is the component that represents the apk, the apkm bundle, or the aab. blint derives them from the decoded AndroidManifest.xml and, for split bundles, from the bundle info.json.
| Property | Object | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal.appPermissions | application component | newline list | When the manifest declares uses-permission entries | The full set of requested Android permissions. This is the primary input for dangerous permission review. |
internal.appFeatures | application component | newline list | When the manifest declares uses-feature entries | The hardware and software features the application requests. |
internal:versionCode | application component | number like | When present in the manifest or bundle info | The integer Android version code, which is more reliable than the display version for ordering builds. |
internal:minSdkVersion | application component | single value | When the manifest declares a minimum SDK | The lowest Android API level the application supports, which bounds the platform hardening that can be assumed. |
internal:targetSdkVersion | application component | single value | When the manifest declares a target SDK | The API level the application targets, which controls many runtime security defaults. |
internal:compileSdkVersion | application component | single value | When present in the manifest | The API level the application was compiled against. |
internal:mainActivity | application component | single value | When a launcher activity is found | The fully qualified launcher activity, useful as an entry point for review. |
internal:appName | application component | single value | For apkm bundles that carry an info.json | The human readable application name from the bundle metadata. |
internal:architectures | application component | comma list | For apkm bundles that declare arches | The native architectures the bundle ships, which indicates the native attack surface present. |
internal:locales | application component | comma list | For apkm bundles that declare languages | The locales packaged in the bundle. |
internal:densities | application component | comma list | For apkm bundles that declare dpis | The screen densities packaged in the bundle. |
File and library component properties
These properties appear on the components that represent the files inside the application, which include native shared objects, dex files, and version stamped maven libraries. They record the discovery evidence and the extracted symbols.
| Property | Object | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal:srcFile | file component | single value | On every file component | The path of the file inside the application archive, used as discovery evidence. |
internal:appFile | file component | single value | On every file component | The originating application file, which ties a split or nested artifact back to its parent. |
internal:functions | dex or shared object component | delimited list | In deep mode for dex, and for shared objects | The method or function names extracted from the artifact. For dex this is the smali style method signature. |
internal:classes | dex component | delimited list | In deep mode for dex files | The class names defined in the dex file. This is the signal that service detection and behavioural review consume. |
Android native library properties
Native libraries are components read from the app's zip in place (A1, the
container model in blint/lib/android_native.py). There is one component
per (library name, version) per logical app: every ABI the app ships for
that library is an occurrence on the same component, so a five-ABI app does
not grow the component count, and the purl carries the ABI list as a
qualifier (pkg:android/libhello.so?abi=arm64-v8a,armeabi-v7a). Component
names keep the file's own name (libapp.so, libc++_shared.so - encoded
by PackageURL in the purl), which is why libapp/libdata from different
vendors no longer collide as app/data. A library's build-id is never
its version.
| Property | Object | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal:abis | native library component | comma list | Always | The ABIs the app ships this library for. Same list as the purl's abi qualifier. |
internal:srcFile | native library component | newline list | Always | Every zip entry the library's bytes were found at, across splits and bundles (deduplicated by (name, sha256)). |
blint:build_id | native library component | per-ABI list | When the ELF carries .note.gnu.build-id | abi:build-id pairs, comma separated. A content hash, not a version - it identifies the exact bytes per ABI, never the component version. |
blint:platform_needed | native library component | comma list | When a DT_NEEDED names an NDK stable-API library | Platform runtime dependencies (libc.so, liblog.so, ...). The platform satisfies them at load time; they are facts, never components. |
NuGet component properties
These properties appear on pkg:nuget components (W3.5). A pkg:nuget purl is emitted only where blint has evidence a NuGet consumer can match: a CLI-header assembly identity for a managed binary, a .deps.json overlay for a published application, or the .nuspec inside a .nupkg archive input. A .dll filename alone produces pkg:generic — over corpus tiers 0/1/5, 217 of 219 pkg:nuget parents the old filename heuristic produced sat on native DLLs with no CLI header (pkg:nuget/python313, pkg:nuget/libcrypto-3), none of them a NuGet package id.
The assembly version and the package version are different numbers (measured over the tier-2 corpus and a 21-package dotnet list package oracle: zero of 50 identities are string-equal, 31 differ semantically — Newtonsoft.Json 13.0.3 ships assembly version 13.0.0.0), so every pkg:nuget component that carries a version states which kind it holds. A package version is never synthesised from an assembly version.
| Property | Object | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal:version_source | every versioned pkg:nuget component | single value | Always, beside a version | assembly_version (the four-part version the CLR binds against) or package_version (stated by a .nuspec, a .deps.json overlay, or a build BOM from --src-dir-boms). The one property a consumer matching these purls against NuGet advisories must read first. |
internal:version_range | pkg:nuget dependency component | single value | .nupkg nuspec dependencies that are not exact pins | The nuspec version grammar as written (9.0.0 is a >= floor, [4.5.0,) an interval). Only an exact pin [1.2.3] becomes a purl version; a floor or interval is a version blint does not have. |
internal:dotnet_assemblyref_state | managed binary parent component | single value | Whenever the file carries a CLI header | read (every AssemblyRef row listed — including a genuine zero, the netmodule shape), capped, or the dotnet block's parse_status (partial, malformed, no_cli_metadata). A managed component with no AssemblyRef components beside it must be distinguishable from one whose table blint could not read. |
internal:nupkg_refusals | .nupkg input component | comma list | When the archive reader refused anything | Every rule-30 limit or hostile shape met while walking the archive (member_path_unsafe, member_is_symlink, member_count_exceeds_cap, total_uncompressed_exceeds_cap, nuspec_member_exceeds_cap, member_depth_exceeds_cap, no_nuspec_member, multiple_nuspec_members, dependencies_listed_capped, archive_unreadable), so a .nupkg that produced no package identity names why instead of vanishing. |
internal:culture | pkg:nuget AssemblyRef component | single value | Non-neutral cultures only | The satellite assembly's culture, e.g. zh-Hans. |
The public key token is a purl qualifier, not a property: pkg:nuget/Newtonsoft.Json@13.0.0.0?token=30ad4fe6b2a6aeed. A token in the purl is matchable by a consumer that has not found the component yet; the same token in a property is only visible after the component is found. Unsigned assemblies and unsigned AssemblyRefs carry no qualifier rather than an empty one.
WebAssembly Component Model properties
These properties appear when blint sbom --wasm-sbom is run over a WebAssembly Component Model binary. blint sbom skips .wasm inputs unless the opt-in is passed, and core modules (non-component wasm) are always skipped because their imports carry no version evidence. The binary itself becomes the parent application component, and each imported WIT package becomes a required library component with a pkg:generic/<namespace>/<package>@<version>?type=wasm purl.
Imports are dependencies and become components. Exports are capabilities the binary provides rather than things it depends on, so they stay a property on the parent.
| Property | Object | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal:binary_type | application component | single value | On every wasm component parent | Always WASM, distinguishing the parent from native binaries in a mixed BOM. |
internal:is_component | application component | boolean | On every wasm component parent | Always true here, since core modules never produce components. Present so a consumer can filter component-model artifacts without re-parsing the binary. |
internal:runtime | application component | single value | When WASI imports are detected | The host runtime interface the binary expects, for example WASI. |
internal:wasi_variants | application component | comma list | When WASI imports are detected | The WASI generations in use (preview1, preview2, preview3), which bounds the host capabilities the binary can reach. |
internal:component_version | application component | single value | When the component header declares a version | The Component Model version from the binary header, for example 13. |
internal:layer_version | application component | single value | When the component header declares a layer | The layer field of the component header, which separates component binaries from core modules sharing the same preamble. |
internal:exported_interfaces | application component | comma list | When the component exports WIT interfaces | The interfaces the binary provides to its host, which is its outward capability surface. |
internal:srcFile | library component | single value | On every wasm interface package component | The wasm file the import evidence came from. |
internal:wit_package | library component | single value | On every wasm interface package component | The WIT package identifier verbatim (wasi:cli), so a reader can grep the component back to the import names in the binary. |
internal:interfaces | library component | comma list | On every wasm interface package component | The specific interfaces imported from that package (wasi:cli/run@0.2.0, ...). Several interfaces of one package at one version collapse into one component. |
Each library component carries occurrence evidence at confidence 0.7: the WIT identifier names the dependency exactly, but the resolved artifact behind it is not verified. Imports without a version keep the package identity with no version field rather than being dropped or guessed.
iOS / macOS application properties
These properties appear when blint generates a BOM for an iOS/macOS app archive (.ipa). The parent application component is identified by the bundle's CFBundleIdentifier and uses a pkg:ios/<bundle-identifier>@<version> purl. Each embedded Mach-O binary (the main executable, frameworks, dylibs, and app extensions) becomes a component keyed by its bundle-relative path (pkg:ios/<identifier>@<version>?path=<bundle-path>).
| Property | Object | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal:bundleName | application component | single value | When the Info.plist declares CFBundleName | The bundle short name. |
internal:bundleDisplayName | application component | single value | When CFBundleDisplayName is present | The user-facing app name shown on the home screen. |
internal:bundleBuild | application component | single value | When CFBundleVersion is present | The build number, which orders builds more reliably than the display version. |
internal:minimumOSVersion | application component | single value | When MinimumOSVersion is present | The lowest OS version the app supports, which bounds the platform hardening that can be assumed. |
internal:platformName | application component | single value | When DTPlatformName is present | The build platform, for example iphoneos. |
internal:platformVersion | application component | single value | When DTPlatformVersion is present | The SDK platform version the app was built against. |
internal:applicationCategory | application component | single value | When LSApplicationCategoryType is present | The App Store application category. |
internal:privacyManifestPresent | application component | boolean | Always emitted for an iOS/macOS app | Whether the app bundle (or any embedded framework/extension) ships a PrivacyInfo.xcprivacy manifest. false means the app's data practices are undeclared, which Apple has required since May 2024. |
internal:privacyTracking | application component | boolean | When a privacy manifest sets NSPrivacyTracking to true | Whether the app (or a bundled SDK) declares that it tracks users across apps and websites. |
internal:privacyUsageDescriptions | application component | comma list | When the Info.plist declares any NS...UsageDescription | The sensitive-data consent strings the app declares (for example NSCameraUsageDescription, NSLocationWhenInUseUsageDescription), indicating the resources it is provisioned to access. |
internal:appQuerySchemes | application component | comma list | When the Info.plist declares LSApplicationQueriesSchemes | The URL schemes the app can probe via canOpenURL to detect other installed apps. A large list signals installed-app enumeration for user profiling. |
internal:bonjourServices | application component | comma list | When the Info.plist declares NSBonjourServices | The Bonjour service types the app browses for, indicating local-network device discovery. |
internal:privacyTrackingDomains | application component | comma list | When a privacy manifest lists NSPrivacyTrackingDomains | The tracking domains declared across the app and its embedded SDKs; connections to these are blocked unless tracking is authorised. |
internal:privacyCollectedDataTypes | application component | comma list | When a privacy manifest lists NSPrivacyCollectedDataTypes | The categories of user data the app and its SDKs declare collecting. |
internal:privacyAccessedAPICategories | application component | comma list | When a privacy manifest lists NSPrivacyAccessedAPITypes | The "required reason" API categories the app declares using (for example user defaults, file timestamps, system boot time). |
internal:role | binary component | single value | On every embedded Mach-O component | The binary's role in the bundle: main, framework, dylib, or plugin. |
internal:bundleIdentifier | binary component | single value | When an embedded framework / extension has its own Info.plist | The real product identifier of a bundled dependency, distinct from the host app identifier. |
internal:provenance | library component | single value | On linked-dylib components in deep mode | apple-platform for OS-provided frameworks (/System, /usr/lib, ...) or bundled for @rpath/@executable_path third-party libraries. Apple platform libraries are also scoped excluded so consumers can filter the OS noise. |
The main executable also carries the binary symbol and import properties documented in the next section, and (in deep mode) its linked dylibs become library components depending on it.
Native binary symbol properties
When blint parses a native binary it records the symbol tables and import and export surfaces as properties so that downstream tools can reason about the linkage without reparsing the binary.
| Property | Value type | What it captures |
|---|---|---|
internal:symtab_symbols | delimited list | The symbol table symbols. |
internal:dynamic_symbols | delimited list | The dynamic symbols. |
internal:exported_symtab_symbols | delimited list | The exported symbol table symbols. |
internal:exported_dynamic_symbols | delimited list | The exported dynamic symbols. |
internal:imports | delimited list | The imported symbols, which indicates external dependency on platform or library functions. |
internal:exports | delimited list | The exported symbols. |
internal:export_functions | delimited list | The exported functions. |
internal:symbol_version | single value | The symbol version record for a symbol. |
internal:symbols_version | single value | The aggregate symbol version information. |
internal:libPath | single value | The library path recorded for the binary. |
internal:hash_path | single value | The path used for the binary hash lookup. |
internal:serviceable | boolean | Whether the binary is considered serviceable. |
blintdb component attribution properties
These properties appear on library components produced from the local blintdb database (blint sbom --use-blintdb). internal:blintdb_attribution names the evidence layer that earned the component its place, so a reader can tell the granularities apart without consulting anything else: whole_binary (symbols or hashes matched at binary granularity), member (static-archive member evidence), or whole_binary+member (both layers independently agree).
| Property | Value type | When emitted | What it captures |
|---|---|---|---|
internal:blintdb_attribution | single value | On every blintdb-matched component | The evidence layer that surfaced the component: whole_binary, member, or whole_binary+member. |
internal:blintdb_fuzzy_layer | single value | On every blintdb-matched component | Named state of the similarity-hash layer: active, or one of the unavailable_* / inactive_* reasons it could not run (a v2 database, unpopulated columns, a run without disassembly). |
internal:blintdb_member_layer | single value | When archive-member evidence contributed to the component | Named state of the member-level layer: active, or the unavailable_* / inactive_* reason it could not run (no member rows in the database, no disassembly, no function hashes). |
internal:blintdb_matched_member_count | number like | When member evidence contributed | How many archive members qualified. |
internal:blintdb_member_names | comma list | When member evidence contributed | The qualified member object names (deflate.o, inflate.o, ...). |
internal:blintdb_member_details | comma list | When member evidence contributed | Per-member evidence: coverage measured against the member's own function population, distinct fuzzy and exact hash matches, the address-adjacency (contiguity) ratio, and which qualification path the member passed. |
internal:blintdb_version_evidence | single value | When the database held several versions of the matched project | The artifact evidence that picked this version and the versions it ruled out, for example path=3.6.3 separated 3.6.3 from 3.6.4. Evidence comes from the install path, the artifact's own SONAME, or a version banner naming the project (a banner that did not qualify as vendored code still names a version, so it separates candidates the same way — it claims no code). The versions of linked dylibs are never used, because they describe dependencies. |
internal:blintdb_version_ambiguity | single value | When artifact evidence could not choose between several versions | The candidate versions with their scores, plus the evidence consulted. The component's purl carries no version in this case. The score alone never picks a version. |
Member-level matching is what attributes statically linked binaries: a stripped static binary has no imports and no symbol names, but its archive members are present whole, so function hashes matched against the database's per-member fingerprints recover the embedded library. Component candidates qualified only by member evidence carry the member attribution value and the per-member evidence above; they never relax any whole-binary gate.
Vendored banner properties
These properties come from vendored-source banner detection: version strings a vendored library copy leaves in the binary (for example deflate 1.3.1 Copyright ...). Every signature requires the version to appear inside a string that also names the library, so a bare version-like string is never a banner. A matched banner becomes a mention instead of a component only when the artifact defines none of that library's API and shows the code living elsewhere: it links the library's shared object (DT_NEEDED / LC_LOAD_DYLIB) or imports its API. That is the stale build-time banner shape, for example a zlib banner in a binary that links libz.1.dylib. Mentions are recorded on the parent component, not emitted as dependencies. A stripped image that links neither keeps the banner as a component, because hidden-visibility vendored code defines nothing in the dynamic symbol table. Banner evidence is independent of blintdb; a banner for a library blintdb already attributed is recorded on that component as corroboration instead of a duplicate.
| Property | Value type | When emitted | What it captures |
|---|---|---|---|
internal:vendored_banner_layer | single value | On banner components (and corroborated ones) | Named state of the banner scan: active, or inactive_no_strings when the binary had no strings. |
internal:vendored_attribution | single value | On banner components | Always vendored_banner, distinguishing banner-attributed components from hash-attributed ones. |
internal:vendored_banner | single value | On banner components | The matched banner string, truncated, as the evidence for the version claim. |
internal:vendored_banner_api_symbols | single value | On banner components | How many of the library's own API symbols the artifact defines, corroborating the banner as code and not just a string. 0 means nothing corroborated it but nothing showed the code living elsewhere either — the stripped case, where the banner is the only witness left. |
internal:vendored_banner_mentions | single value | On the parent component when a banner-shaped string was not corroborated | Each mention as purl (string only; no <library> API symbols defined in this artifact). Recorded for visibility; never a dependency component. |
ELF ABI and runtime dependency properties
These properties come from the ELF ABI analysis. Most sit on the parent component and describe the binary as a whole. The last three sit on the library components recovered for runtime-loaded dependencies. See abi_analysis for how the values are derived.
| Property | Component | Value type | When emitted | What it captures |
|---|---|---|---|---|
internal:abi_libc | parent component | single value | When the C library can be determined | glibc, musl, or bionic. Tells a consumer whether the binary can run in a musl-based or distroless image at all. |
internal:abi_min_glibc_version | parent component | single value | When any glibc version node is bound | The oldest glibc the binary can run on. A deployment target older than this fails at load time. |
internal:abi_portability_notes | parent component | single value | When the analysis produced any note | Human-readable sentences describing every portability constraint found, suitable for direct display. |
internal:missing_dependencies | parent component | comma list | Only with link closure resolution enabled | Sonames nothing on the search path supplies. Each is a load-time failure and usually an undeclared packaging dependency. |
internal:unresolved_symbol_count | parent component | number like | Only with link closure resolution enabled | How many imported symbols no library in the closure defines, which normally indicates version skew rather than a missing library. |
internal:unused_dependencies | parent component | comma list | When symbols can be attributed to libraries | Declared dependencies from which no symbol is imported. They still appear in every downstream inventory and produce vulnerability matches against code the binary never calls. |
internal:undeclared_dependencies | parent component | comma list | When symbols can be attributed to libraries | Libraries supplying symbols without being declared. They are real components of the binary and are missing from any inventory built from the declared dependencies. |
internal:abi_requirements | parent component | single value | In deep mode, when any version provider is bound | One entry per provider, PROVIDER>=min_version (N symbols) [set by ...], for example GLIBC>=2.34 (3 symbols) [set by __libc_start_main]. The imports named after set by are the ones that set the floor. |
internal:soname | library component | single value | On every recovered runtime-loaded dependency | The soname exactly as it appears in the binary, including the version suffix that the component name drops. |
internal:load_kind | library component | single value | On every recovered runtime-loaded dependency | runtime, meaning the library is opened on demand rather than linked. These components are marked with the optional scope because the binary runs without them. |
internal:evidence | library component | single value | On every recovered runtime-loaded dependency | Why the dependency was recovered: which loading entry point is imported, which section the name literal is in, and any absolute path found. |
ABI floors are requirements on the execution environment, not components. GLIBC_2.34 names an interface version, and the libc actually installed (for example glibc 2.41 on Debian trixie) is a different artifact. A pkg:generic/gnu/libc@2.34 component would therefore claim an identity no artifact has, and vulnerability matching would compare it wrongly in both directions. Earlier versions of blint emitted these floors as components; they are now internal:abi_requirements on the parent, and the raw version nodes are in internal:symbols_version.
Dalvik behavioural properties
When deep mode is enabled, blint disassembles the dex methods using its Dalvik disassembler and runs a behavioural review over the decoded instructions. The review resolves the constant pool so that invoke targets, field accesses, and string constants appear as readable descriptors, then matches them against a set of behavioural rules. The findings are attached to the dex component that produced them.
There are two related properties. The summary property lists the triggered rule identifiers. Each triggered rule then gets its own detail property whose value is a compound string of the severity, the number of sites, and one example of the evidence.
| Property | Object | Value type | What it captures |
| ------------------------- | ------------- | -------------- | ------------------------------------------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| internal:behaviours | dex component | comma list | The identifiers of the behavioural rules that triggered in this dex file. |
| internal:behaviour:<ID> | dex component | compound value | One entry per triggered rule. The value is severity | count | samplewhere severity is one of critical, high, medium, low, or info, count is the number of matched evidence entries recorded for the rule, and sample is one example of the matched descriptor or string. |
The behavioural rules that can appear as the <ID> suffix are listed below together with the behaviour they detect and the default severity.
| Rule identifier | Severity | Behaviour |
|---|---|---|
ANDROID_DYNAMIC_CODE_LOADING | high | The application loads code at runtime through a dex class loader. |
ANDROID_REFLECTION | medium | The application resolves classes or methods through Java reflection. |
ANDROID_NATIVE_EXEC | high | The application executes native commands or loads native libraries. |
ANDROID_WEAK_CRYPTO | high | The application references a weak or broken cryptographic primitive such as DES, RC4, MD5, SHA-1, or the insecure ECB cipher mode. |
ANDROID_CLEARTEXT_TRAFFIC | medium | The application references cleartext http endpoints. |
ANDROID_WEBVIEW_UNSAFE | medium | The application configures a WebView with unsafe settings such as JavaScript or file access. |
ANDROID_RAW_SQL | low | The application executes raw SQL that may be injectable. |
ANDROID_DEVICE_IDENTIFIERS | medium | The application reads persistent device identifiers. |
ANDROID_SMS_ACCESS | high | The application sends or reads SMS messages. |
ANDROID_INSTALLED_APPS_ENUM | low | The application enumerates other installed applications. |
ANDROID_ROOT_DETECTION | info | The application probes for root or superuser binaries. |
ANDROID_REMOTE_AI_SERVICE | medium | The application calls a remote AI or LLM inference SDK or endpoint such as OpenAI, Anthropic, Gemini, or Bedrock. |
ANDROID_ON_DEVICE_AI | info | The application runs on-device inference through a framework such as TensorFlow Lite, ML Kit, ONNX Runtime, or PyTorch, or bundles a local model artifact. |
ANDROID_TRACKER_SDK | low | The application embeds an analytics, crash reporting, or attribution tracker SDK. |
ANDROID_AD_NETWORK | low | The application embeds an advertising or adware SDK. |
These rules are defined in the annotation file blint/data/annotations/review_methods_android.yml and keyed by the dexbinary exe type. They are loaded and matched by the same rule loader and pattern review engine that blint uses for native binary reviews, so the rule set can be extended by editing that file or by supplying a custom rules directory.
atom-tools reads these properties back from the BOM, aggregates the counts for the same rule across all dex files, and presents them as static behaviours alongside the reachability findings from atom. The behaviours are a static signal, so they describe what the code can do rather than what was proven reachable.
Service properties
Services represent the remote endpoints and third party SDKs that the application talks to. blint produces services statically by matching dex class names against its bundled service and tracker catalogs. atom-tools produces services from reachability by reading the chen tags on the flows of a reachable slice. The two sets are merged by service reference, so a service that is both bundled and reachable carries the static detection and the reachability evidence together.
Each service uses a bom reference of the form service:<Name>. The x-trust-boundary field is set to indicate whether the service crosses a trust boundary, and the data array records the observed data flow direction and classification.
| Property or field | Object | Value type | Producer | What it captures |
|---|---|---|---|---|
internal:detection | service | single value | blint | The detection method. The value static indicates presence based detection from class names rather than proven reachability. |
internal:serviceKind | service | single value | blint | Whether the entry is a service or a tracker, taken from which catalog matched. |
internal:reachableFlows | service | number like | atom-tools | The number of reachable flows that attribute traffic to this service. |
internal:onDeviceAi | service | boolean | atom-tools | Set to true when the service represents on device inference rather than a remote call. |
data.flow | service data entry | enum string | atom-tools | The data flow direction relative to the service. The value is inbound when the application sends to a remote service, outbound when a remote service sends to the device, bi-directional when both occur, and unknown for static detections that carry no reachability. |
x-trust-boundary | service | boolean | both | Whether the service crosses a trust boundary. On device services do not cross a trust boundary, so this is false for them. |
The chen tag namespaces that drive the atom-tools service and tracker attribution are summarized here for context, since they are the upstream source of the service classifications even though they are not written directly into the BOM as properties. The personally identifiable information family uses tags such as pii, pii-email, pii-device-id, and pii-national-id. The regulated data families use pci-dss with the pci-card-* detail tags, phi-medical, and the regional regimes gdpr, ccpa, and hipaa. The financial family uses finance-iban, finance-bank-account, and finance-crypto-wallet. Secrets use secret with detail tags such as secret-aws-access-key and secret-jwt. Network direction uses service-egress, service-ingress, and on-device-ai. Third party SDKs use tracker with a category.
The Dalvik callgraph sidecar
When disassembly is enabled, blint writes a callgraph next to the BOM rather than inside it, because a full callgraph is large. The file is named <bom-stem>-<app>.dex-callgraph.json and uses the same node and edge shape as the native binary callgraph that blint produces for ELF, PE, and Mach-O. Each node carries the method index, the resolved descriptor, and a flag that records whether the method has a body in the analyzed dex. Each edge is a caller to callee pair derived from the invoke instructions. The callgraph can be loaded by the blint callgraph tooling and exported to DOT or GraphML. atom-tools records the path to this sidecar in the report it writes so that the callgraph can be located alongside the other artifacts.
The iOS/macOS callgraph sidecars
For an iOS/macOS app archive, disassembly is performed per embedded Mach-O, so blint writes one callgraph sidecar per binary that yields a graph. Each file is named <bom-stem>-<app>-<bundle-path>.callgraph.json (the bundle-relative path is slugified into the filename) and uses the same node and edge shape as the native ELF/PE/Mach-O callgraph. FairPlay-encrypted binaries are skipped because their __TEXT cannot be disassembled. As with the Dalvik sidecar, these files can be loaded by the blint callgraph tooling and exported to DOT, GraphML, or GEXF.
Using these properties
The application metadata properties support manifest review without a second decode of the apk. A policy can read internal:minSdkVersion to decide whether platform hardening can be assumed, and internal.appPermissions to flag dangerous permission combinations.
The behavioural properties support static triage. A high severity behaviour such as ANDROID_NATIVE_EXEC or ANDROID_DYNAMIC_CODE_LOADING is a strong candidate for review, and the compound value gives an example call site so that a reviewer can start from concrete evidence. Because the behaviours are static, they are best used together with the reachability findings from atom, where a behaviour that is also reachable is higher confidence than one that is only present.
The service properties support data flow and third party review. The combination of internal:detection set to static and a nonzero internal:reachableFlows distinguishes an SDK that is merely bundled from one that the application actually uses, and the data.flow direction supports egress and ingress analysis.
Windows component identity (W4.5)
This section documents the purl schemes and identity properties blint uses for Windows inputs. The principle across all of them: the purl states only what the container or the binary itself declares, and every naming decision carries its evidence beside it, so a consumer can always tell a resource-named component from a filename-named one.
Native PE components (pkg:generic)
A native PE's parent component is named from its OriginalFilename VERSIONINFO resource when one is present (renaming a file is free; the resource is not), else from the on-disk name. The decision is visible:
| Property | Meaning |
|---|---|
internal:filename_original | The OriginalFilename the binary declares. |
internal:filename_on_disk | The on-disk name, carried only when it differs from the resource — a mismatch is evidence, not noise. |
internal:version_source | version_info (the resource), package_identity (a manifest), deployment_identity (ClickOnce), assembly_version/package_version (managed). |
internal:signer_cn | The Authenticode signer common name, when a signature block parsed. |
internal:signing_class | The W2.4 signing class (commercial_ov, microsoft_1st_party, self_signed, ...). Absent when undetermined — absence never reads as unsigned. |
The purl stays pkg:generic unless a build BOM (--src-dir-boms) names the package: the overlay is consulted under the on-disk stem, the resource stem, and the pkg:nuget/<name> keys the BOM store holds, and an overlay hit may yield a pkg:nuget purl because a BOM naming the package is evidence, unlike a filename.
MSIX/Appx packages (pkg:appx)
The purl type pkg:appx is blint's documented scheme for Windows app packages (the purl spec's type list has no appx entry; pkg:ios/pkg:macos set the in-repo precedent for platform scheme types). The namespace-less form is pkg:appx/<Identity Name>@<Identity Version> — the exact strings from AppxManifest.xml / AppxBundleManifest.xml. The publisher distinguished name rides as the internal:appxPublisher property rather than a purl namespace (the DN is long and percent-encoding it would make the purl unmatchable), and internal:version_source=package_identity marks the version as the manifest's.
MSI databases (pkg:generic)
An .msi parent is pkg:generic/<ProductName>@<ProductVersion> with the codes as properties: internal:msiProductCode (GUID), internal:msiUpgradeCode, internal:msiPackageCode (the summary information's revision-number GUID), and internal:msiManufacturer. No pkg:msi type exists in the purl spec and none is invented here.
ClickOnce deployments (pkg:generic)
A .application manifest yields pkg:generic/<assemblyIdentity name>@<version> with the public key token as a public_key_token qualifier, plus internal:clickonceUpdateUrl and internal:clickoncePublisher.