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.

ShapeEncodingExamples
Booleanthe strings true or falseinternal:onDeviceAi
Number likea decimal stringinternal:versionCode, internal:reachableFlows
Single valuea plain stringinternal:minSdkVersion, internal:mainActivity
Newline separated listvalues joined by a newlineinternal.appPermissions, internal.appFeatures
Delimited listvalues joined by the blint symbol delimiter ~~internal:functions, internal:classes
Compound valuefields joined by the pipe characterinternal: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.

PropertyObjectValue typeWhen emittedWhat it captures
internal.appPermissionsapplication componentnewline listWhen the manifest declares uses-permission entriesThe full set of requested Android permissions. This is the primary input for dangerous permission review.
internal.appFeaturesapplication componentnewline listWhen the manifest declares uses-feature entriesThe hardware and software features the application requests.
internal:versionCodeapplication componentnumber likeWhen present in the manifest or bundle infoThe integer Android version code, which is more reliable than the display version for ordering builds.
internal:minSdkVersionapplication componentsingle valueWhen the manifest declares a minimum SDKThe lowest Android API level the application supports, which bounds the platform hardening that can be assumed.
internal:targetSdkVersionapplication componentsingle valueWhen the manifest declares a target SDKThe API level the application targets, which controls many runtime security defaults.
internal:compileSdkVersionapplication componentsingle valueWhen present in the manifestThe API level the application was compiled against.
internal:mainActivityapplication componentsingle valueWhen a launcher activity is foundThe fully qualified launcher activity, useful as an entry point for review.
internal:appNameapplication componentsingle valueFor apkm bundles that carry an info.jsonThe human readable application name from the bundle metadata.
internal:architecturesapplication componentcomma listFor apkm bundles that declare archesThe native architectures the bundle ships, which indicates the native attack surface present.
internal:localesapplication componentcomma listFor apkm bundles that declare languagesThe locales packaged in the bundle.
internal:densitiesapplication componentcomma listFor apkm bundles that declare dpisThe 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.

PropertyObjectValue typeWhen emittedWhat it captures
internal:srcFilefile componentsingle valueOn every file componentThe path of the file inside the application archive, used as discovery evidence.
internal:appFilefile componentsingle valueOn every file componentThe originating application file, which ties a split or nested artifact back to its parent.
internal:functionsdex or shared object componentdelimited listIn deep mode for dex, and for shared objectsThe method or function names extracted from the artifact. For dex this is the smali style method signature.
internal:classesdex componentdelimited listIn deep mode for dex filesThe 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.

PropertyObjectValue typeWhen emittedWhat it captures
internal:abisnative library componentcomma listAlwaysThe ABIs the app ships this library for. Same list as the purl's abi qualifier.
internal:srcFilenative library componentnewline listAlwaysEvery zip entry the library's bytes were found at, across splits and bundles (deduplicated by (name, sha256)).
blint:build_idnative library componentper-ABI listWhen the ELF carries .note.gnu.build-idabi:build-id pairs, comma separated. A content hash, not a version - it identifies the exact bytes per ABI, never the component version.
blint:platform_needednative library componentcomma listWhen a DT_NEEDED names an NDK stable-API libraryPlatform 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.

PropertyObjectValue typeWhen emittedWhat it captures
internal:version_sourceevery versioned pkg:nuget componentsingle valueAlways, beside a versionassembly_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_rangepkg:nuget dependency componentsingle value.nupkg nuspec dependencies that are not exact pinsThe 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_statemanaged binary parent componentsingle valueWhenever the file carries a CLI headerread (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 componentcomma listWhen the archive reader refused anythingEvery 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:culturepkg:nuget AssemblyRef componentsingle valueNon-neutral cultures onlyThe 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.

PropertyObjectValue typeWhen emittedWhat it captures
internal:binary_typeapplication componentsingle valueOn every wasm component parentAlways WASM, distinguishing the parent from native binaries in a mixed BOM.
internal:is_componentapplication componentbooleanOn every wasm component parentAlways true here, since core modules never produce components. Present so a consumer can filter component-model artifacts without re-parsing the binary.
internal:runtimeapplication componentsingle valueWhen WASI imports are detectedThe host runtime interface the binary expects, for example WASI.
internal:wasi_variantsapplication componentcomma listWhen WASI imports are detectedThe WASI generations in use (preview1, preview2, preview3), which bounds the host capabilities the binary can reach.
internal:component_versionapplication componentsingle valueWhen the component header declares a versionThe Component Model version from the binary header, for example 13.
internal:layer_versionapplication componentsingle valueWhen the component header declares a layerThe layer field of the component header, which separates component binaries from core modules sharing the same preamble.
internal:exported_interfacesapplication componentcomma listWhen the component exports WIT interfacesThe interfaces the binary provides to its host, which is its outward capability surface.
internal:srcFilelibrary componentsingle valueOn every wasm interface package componentThe wasm file the import evidence came from.
internal:wit_packagelibrary componentsingle valueOn every wasm interface package componentThe WIT package identifier verbatim (wasi:cli), so a reader can grep the component back to the import names in the binary.
internal:interfaceslibrary componentcomma listOn every wasm interface package componentThe 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>).

PropertyObjectValue typeWhen emittedWhat it captures
internal:bundleNameapplication componentsingle valueWhen the Info.plist declares CFBundleNameThe bundle short name.
internal:bundleDisplayNameapplication componentsingle valueWhen CFBundleDisplayName is presentThe user-facing app name shown on the home screen.
internal:bundleBuildapplication componentsingle valueWhen CFBundleVersion is presentThe build number, which orders builds more reliably than the display version.
internal:minimumOSVersionapplication componentsingle valueWhen MinimumOSVersion is presentThe lowest OS version the app supports, which bounds the platform hardening that can be assumed.
internal:platformNameapplication componentsingle valueWhen DTPlatformName is presentThe build platform, for example iphoneos.
internal:platformVersionapplication componentsingle valueWhen DTPlatformVersion is presentThe SDK platform version the app was built against.
internal:applicationCategoryapplication componentsingle valueWhen LSApplicationCategoryType is presentThe App Store application category.
internal:privacyManifestPresentapplication componentbooleanAlways emitted for an iOS/macOS appWhether 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:privacyTrackingapplication componentbooleanWhen a privacy manifest sets NSPrivacyTracking to trueWhether the app (or a bundled SDK) declares that it tracks users across apps and websites.
internal:privacyUsageDescriptionsapplication componentcomma listWhen the Info.plist declares any NS...UsageDescriptionThe sensitive-data consent strings the app declares (for example NSCameraUsageDescription, NSLocationWhenInUseUsageDescription), indicating the resources it is provisioned to access.
internal:appQuerySchemesapplication componentcomma listWhen the Info.plist declares LSApplicationQueriesSchemesThe URL schemes the app can probe via canOpenURL to detect other installed apps. A large list signals installed-app enumeration for user profiling.
internal:bonjourServicesapplication componentcomma listWhen the Info.plist declares NSBonjourServicesThe Bonjour service types the app browses for, indicating local-network device discovery.
internal:privacyTrackingDomainsapplication componentcomma listWhen a privacy manifest lists NSPrivacyTrackingDomainsThe tracking domains declared across the app and its embedded SDKs; connections to these are blocked unless tracking is authorised.
internal:privacyCollectedDataTypesapplication componentcomma listWhen a privacy manifest lists NSPrivacyCollectedDataTypesThe categories of user data the app and its SDKs declare collecting.
internal:privacyAccessedAPICategoriesapplication componentcomma listWhen a privacy manifest lists NSPrivacyAccessedAPITypesThe "required reason" API categories the app declares using (for example user defaults, file timestamps, system boot time).
internal:rolebinary componentsingle valueOn every embedded Mach-O componentThe binary's role in the bundle: main, framework, dylib, or plugin.
internal:bundleIdentifierbinary componentsingle valueWhen an embedded framework / extension has its own Info.plistThe real product identifier of a bundled dependency, distinct from the host app identifier.
internal:provenancelibrary componentsingle valueOn linked-dylib components in deep modeapple-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.

PropertyValue typeWhat it captures
internal:symtab_symbolsdelimited listThe symbol table symbols.
internal:dynamic_symbolsdelimited listThe dynamic symbols.
internal:exported_symtab_symbolsdelimited listThe exported symbol table symbols.
internal:exported_dynamic_symbolsdelimited listThe exported dynamic symbols.
internal:importsdelimited listThe imported symbols, which indicates external dependency on platform or library functions.
internal:exportsdelimited listThe exported symbols.
internal:export_functionsdelimited listThe exported functions.
internal:symbol_versionsingle valueThe symbol version record for a symbol.
internal:symbols_versionsingle valueThe aggregate symbol version information.
internal:libPathsingle valueThe library path recorded for the binary.
internal:hash_pathsingle valueThe path used for the binary hash lookup.
internal:serviceablebooleanWhether 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).

PropertyValue typeWhen emittedWhat it captures
internal:blintdb_attributionsingle valueOn every blintdb-matched componentThe evidence layer that surfaced the component: whole_binary, member, or whole_binary+member.
internal:blintdb_fuzzy_layersingle valueOn every blintdb-matched componentNamed 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_layersingle valueWhen archive-member evidence contributed to the componentNamed 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_countnumber likeWhen member evidence contributedHow many archive members qualified.
internal:blintdb_member_namescomma listWhen member evidence contributedThe qualified member object names (deflate.o, inflate.o, ...).
internal:blintdb_member_detailscomma listWhen member evidence contributedPer-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_evidencesingle valueWhen the database held several versions of the matched projectThe 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_ambiguitysingle valueWhen artifact evidence could not choose between several versionsThe 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.

PropertyValue typeWhen emittedWhat it captures
internal:vendored_banner_layersingle valueOn banner components (and corroborated ones)Named state of the banner scan: active, or inactive_no_strings when the binary had no strings.
internal:vendored_attributionsingle valueOn banner componentsAlways vendored_banner, distinguishing banner-attributed components from hash-attributed ones.
internal:vendored_bannersingle valueOn banner componentsThe matched banner string, truncated, as the evidence for the version claim.
internal:vendored_banner_api_symbolssingle valueOn banner componentsHow 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_mentionssingle valueOn the parent component when a banner-shaped string was not corroboratedEach 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.

PropertyComponentValue typeWhen emittedWhat it captures
internal:abi_libcparent componentsingle valueWhen the C library can be determinedglibc, musl, or bionic. Tells a consumer whether the binary can run in a musl-based or distroless image at all.
internal:abi_min_glibc_versionparent componentsingle valueWhen any glibc version node is boundThe oldest glibc the binary can run on. A deployment target older than this fails at load time.
internal:abi_portability_notesparent componentsingle valueWhen the analysis produced any noteHuman-readable sentences describing every portability constraint found, suitable for direct display.
internal:missing_dependenciesparent componentcomma listOnly with link closure resolution enabledSonames nothing on the search path supplies. Each is a load-time failure and usually an undeclared packaging dependency.
internal:unresolved_symbol_countparent componentnumber likeOnly with link closure resolution enabledHow many imported symbols no library in the closure defines, which normally indicates version skew rather than a missing library.
internal:unused_dependenciesparent componentcomma listWhen symbols can be attributed to librariesDeclared 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_dependenciesparent componentcomma listWhen symbols can be attributed to librariesLibraries 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_requirementsparent componentsingle valueIn deep mode, when any version provider is boundOne 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:sonamelibrary componentsingle valueOn every recovered runtime-loaded dependencyThe soname exactly as it appears in the binary, including the version suffix that the component name drops.
internal:load_kindlibrary componentsingle valueOn every recovered runtime-loaded dependencyruntime, 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:evidencelibrary componentsingle valueOn every recovered runtime-loaded dependencyWhy 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 identifierSeverityBehaviour
ANDROID_DYNAMIC_CODE_LOADINGhighThe application loads code at runtime through a dex class loader.
ANDROID_REFLECTIONmediumThe application resolves classes or methods through Java reflection.
ANDROID_NATIVE_EXEChighThe application executes native commands or loads native libraries.
ANDROID_WEAK_CRYPTOhighThe application references a weak or broken cryptographic primitive such as DES, RC4, MD5, SHA-1, or the insecure ECB cipher mode.
ANDROID_CLEARTEXT_TRAFFICmediumThe application references cleartext http endpoints.
ANDROID_WEBVIEW_UNSAFEmediumThe application configures a WebView with unsafe settings such as JavaScript or file access.
ANDROID_RAW_SQLlowThe application executes raw SQL that may be injectable.
ANDROID_DEVICE_IDENTIFIERSmediumThe application reads persistent device identifiers.
ANDROID_SMS_ACCESShighThe application sends or reads SMS messages.
ANDROID_INSTALLED_APPS_ENUMlowThe application enumerates other installed applications.
ANDROID_ROOT_DETECTIONinfoThe application probes for root or superuser binaries.
ANDROID_REMOTE_AI_SERVICEmediumThe application calls a remote AI or LLM inference SDK or endpoint such as OpenAI, Anthropic, Gemini, or Bedrock.
ANDROID_ON_DEVICE_AIinfoThe 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_SDKlowThe application embeds an analytics, crash reporting, or attribution tracker SDK.
ANDROID_AD_NETWORKlowThe 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 fieldObjectValue typeProducerWhat it captures
internal:detectionservicesingle valueblintThe detection method. The value static indicates presence based detection from class names rather than proven reachability.
internal:serviceKindservicesingle valueblintWhether the entry is a service or a tracker, taken from which catalog matched.
internal:reachableFlowsservicenumber likeatom-toolsThe number of reachable flows that attribute traffic to this service.
internal:onDeviceAiservicebooleanatom-toolsSet to true when the service represents on device inference rather than a remote call.
data.flowservice data entryenum stringatom-toolsThe 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-boundaryservicebooleanbothWhether 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:

PropertyMeaning
internal:filename_originalThe OriginalFilename the binary declares.
internal:filename_on_diskThe on-disk name, carried only when it differs from the resource — a mismatch is evidence, not noise.
internal:version_sourceversion_info (the resource), package_identity (a manifest), deployment_identity (ClickOnce), assembly_version/package_version (managed).
internal:signer_cnThe Authenticode signer common name, when a signature block parsed.
internal:signing_classThe 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.