先看结论与判断条件

  • Remote Config 参数会在客户端被采用和观察,不能存放秘密,也不能独自授予高价值操作权限。
  • 默认值、已获取值、已激活值和当前生效值是不同状态,门禁必须覆盖首次启动、离线、失败与旧缓存。
  • 条件优先级决定最终参数,版本、地区、用户属性和随机百分位条件需要在变体与测试矩阵中明确。
  • VMP 候选应是稳定、敏感且可独立回归的本地决策核心,不是 fetch、activate、序列化或 SDK 回调。
  • 远端参数可以收紧本地能力或提供风险输入,但服务端授权的对象、主体和动作不能下放给客户端布尔值。
  • rollout 与紧急停用都有传播延迟和离线边界,回滚参数不能恢复已损坏状态或替代候选回滚。

先把配置分发与安全裁决分开

Remote Config 解决的是参数分发与条件选择,不是建立可信执行环境。Android 客户端最终要取得、缓存、激活并读取参数,运行中的值能够被观察,相关调用路径也位于用户控制的设备上。因此它适合控制功能呈现、实验参数、风险阈值和紧急收敛,不适合保存密钥,也不适合用一个 allowPurchase 或 isAdmin 参数直接授予敏感操作。

VMP 的作用也要放在正确层级。它可以提高本地决策代码被直接阅读和修改的成本,适合保护风险评分、状态组合、离线限制和业务关键分支;它不能让远端参数变成秘密,不能保证设备一定取得最新值,也不能替代服务端按用户、对象和动作执行授权。若服务器只相信客户端返回的“远端配置允许”,整个授权链仍由公共客户端控制。

架构评审应把链路拆成参数定义、默认值、fetch、activate、条件解析、本地决策、敏感动作和服务端裁决。每一步记录输入、失败状态和责任人,再选择保护边界。SDK 获取与缓存属于易变的平台胶水,通常保留原状;稳定的本地业务决策可以纳入 VMP;高价值权限和数据访问必须由可信服务端再次确认。

Remote Config 链路的责任划分
环节主要责任是否适合 VMP不能承担
默认值定义首次启动和失败行为随决策一起评审证明远端已同步
fetch获取并缓存远端参数通常排除使新值立即生效
activate切换当前生效参数集通常排除替代业务回滚
条件选择确定目标参数值记录输入与顺序证明客户端已拉取
本地决策组合状态、阈值和限制敏感核心可保护完成服务端授权
服务端裁决校验主体、对象和动作不在客户端 VMP 范围依赖客户端布尔值

默认值决定首次启动和故障下限

Firebase Remote Config for Android 要求应用提供应用内默认值,并按 fetch 与 activate 生命周期采用远端参数。默认值不是开发占位,它会在首次安装、尚未获取、获取失败或参数缺失时直接影响行为。安全评审应逐项回答:默认值是否放宽敏感能力,旧客户端是否理解新参数,类型转换失败时走哪条路径,以及没有已激活值时是否能保持可接受的最小功能。

安全关键参数应优先采用失败关闭或服务端确认,而不是默认开放。例如远端参数可以决定是否显示某个入口,但真正提交高价值操作时,服务器仍需验证权限;远端参数也可以降低本地风险阈值,却不应在网络失败时把阈值恢复到无限制。默认值的目标不是让页面永远可用,而是在不确定状态下保持已声明的风险边界。

默认值还要按构建变体核对。开发变体可能默认打开诊断入口,生产变体必须具有不同的资源、applicationId 或参数文件。Android build variants 说明 build type、product flavor 和 source set 会组合成不同变体,同一仓库不代表默认值相同。门禁应读取最终候选中的默认资源,并把结果绑定到具体 variant 与产物摘要。

Remote Config 生命周期状态的安全处理
状态可用值来源建议行为禁止假设
首次启动未 fetch应用内默认值执行安全默认路径远端值马上可用
fetch 成功未 activate旧激活值或默认值保持当前状态并记录待激活获取即等于生效
activate 成功新激活参数集重算受影响决策所有业务状态自动恢复
fetch 失败旧激活值或默认值按陈旧期限与失败策略处理失败时默认开放
activate 失败当前激活值保持旧状态并报告异常部分字段安全切换
参数缺失或类型错误显式回退值失败关闭或请求服务端SDK 会自动纠正语义

fetch、activate 和 current value 不能混成一个状态

fetch 负责取得并缓存候选参数,activate 负责让缓存参数成为当前值。若监控只记录 fetch success,就可能把尚未激活的值当作生效配置;若只记录最终布尔值,又无法判断它来自应用默认值、旧缓存还是新远端值。关键决策应记录参数 key、值来源、fetch 状态、activate 状态、模板或配置版本以及观察时间,但日志不应包含令牌和个人数据。

陈旧缓存需要明确策略。对纯展示参数,继续使用上次激活值通常可接受;对风险阈值,要设置可辩护的有效期并在过期后收紧;对依赖服务端授权的动作,客户端缓存只影响界面或预检查,服务器仍按当前规则拒绝未授权请求。用一个通用 keep-last-value 处理所有参数,会把产品连续性偏好误用到安全边界。

激活通常会同时影响一组参数,业务代码却可能在不同时间读取它们。若决策需要多个 key 保持一致,应在本地创建不可变快照并验证 schema 版本,再一次性送入决策函数。不要在一次判断中多次从 SDK 查询可变值,否则 activate 恰好发生时可能得到混合版本。VMP 可以保护快照后的决策逻辑,但不能修复上游读取的非原子性。

条件顺序本身就是决策输入

Firebase Remote Config conditions 说明参数可以按应用版本、平台、国家地区、用户属性和随机百分位等条件选择值,条件顺序会影响最终结果。团队不能只审查默认值和某个命中值,还要保存条件列表、优先级与兜底值。两个条件同时匹配时谁先决定结果,必须在测试用例中可复现,不能依赖控制台截图和操作者记忆。

条件中的用户属性和地区并不等于授权身份。它们可用于目标群体、体验差异和风险输入,但不能证明当前调用者有权读取某个对象或执行交易。特别是客户端能够观察最终值时,攻击者可以了解被分配的策略,并尝试改变本地决策路径。服务端必须重新验证真正的授权条件,客户端条件只能用于提示、预筛选或纵深限制。

随机百分位适合灰度,但测试需要固定代表桶。团队应覆盖默认条件、每个高优先级条件、多个条件同时匹配、无条件匹配以及参数缺失。应用版本条件还要覆盖升级前后,因为旧客户端可能不认识新 schema。测试证据应绑定配置版本、应用候选和输入属性,不能用一台设备的一次命中概括全部人群。

常见条件的可用范围
条件适合用途必须验证不能替代
应用版本兼容开关和迁移节奏旧版、新版与未知版候选身份校验
平台Android 与其他端差异平台默认与缺失字段服务端权限
国家地区合规与功能可见性边界地区和离线状态用户真实身份
用户属性已定义群体体验属性缺失、陈旧和变更对象级授权
随机百分位灰度和实验分桶代表桶与稳定性实际安装覆盖
条件优先级解决多条件命中顺序变化与兜底自动正确合并

按决策类型选择 VMP 保护对象

第一类是展示与体验参数,例如页面布局、文案变体和非敏感功能入口。它们通常不值得进入 VMP,保持代码简单更利于升级和诊断。第二类是风险输入,例如阈值、设备状态权重和离线次数上限。若本地组合算法包含商业敏感规则,可把稳定的计算核心纳入保护,同时保持参数读取、类型转换和错误报告在外层。

第三类是收敛参数,例如 disableFeature、requireOnlineCheck 或 tightenLimit。远端配置可以让客户端走更保守路径,但必须考虑离线和缓存延迟;服务器侧仍应能独立拒绝危险操作。第四类是授权结果,例如 allowTransfer、isEntitled 或 canReadObject。这类布尔值不应由 Remote Config 直接承担,即使读取后的 if 分支进入 VMP,也只是把错误授权隐藏得更深。

选择 VMP 候选时看四个条件:逻辑是否敏感、是否稳定、是否可以用确定性输入回归、保护后失败是否可诊断。SDK 回调、Task 链、序列化 DTO、资源 ID、生成代码和 fetch/activate 生命周期都容易随 SDK 变化,应排除。适合保护的是纯函数式决策核心,它接收规范化快照和业务状态,返回允许、拒绝或要求服务端确认,并带可观测原因码。

Remote Config 决策的 VMP 选择矩阵
决策类型参数作用VMP 建议最终安全边界
展示配置改变界面与文案通常排除产品功能测试
非敏感实验选择体验分支通常排除实验与回滚治理
本地风险输入调整阈值或权重保护稳定计算核心服务端复核高价值动作
紧急收敛禁用或限制本地能力保护收敛决策可选服务端独立拒绝
离线限制控制次数、期限和状态可保护离线状态机联网后服务端对账
业务授权决定主体与对象权限不得只保护客户端分支服务端强制裁决

rollout 和 kill switch 都有传播边界

Firebase Remote Config rollouts 支持向部分目标用户逐步开放参数并依据监控调整或回滚。它适合降低变更半径,但不保证每个客户端立刻 fetch 与 activate。离线设备、受节流影响的请求、长期后台进程和未触发激活的会话都可能继续使用旧值。因此紧急停用不能只依赖客户端参数,关键服务应有服务端拒绝路径。

rollout 回滚只改变后续采用的配置值,不能恢复已经修改的本地数据库、已提交的业务动作或损坏的产物。如果新参数触发了不可逆迁移,回退参数可能让旧代码路径面对新数据格式。发布设计应先定义状态兼容与恢复动作,再决定参数能否灰度。控制台上的回滚按钮不是应用事务回滚,也不是 APK 版本召回。

Firebase Remote Config use cases 展示按版本或用户群控制功能暴露等场景,但安全关键默认值与失败路径仍需预先存在于应用。用例可帮助选择配置方式,不能证明某个项目的紧急停用时效、加固兼容或用户覆盖。发布前应在真实候选上模拟未 fetch、fetch 未 activate、旧缓存、目标条件和回滚后重启等路径。

用策略检查器约束默认值和决策映射

下面的 Python 脚本读取一份公开安全的策略映射 JSON,检查生命周期失败策略、参数 key 唯一性、默认值、条件优先级、本地决策函数和服务端强制字段。每个参数标记 decisionClass、failurePolicy、conditions、decisionFunction、vmpScope 与 serverEnforced。脚本不连接 Firebase,不读取真实用户属性,也不输出配置值,适合在代码审查前发现设计责任混乱。

授权类参数必须使用布尔 false 默认值,必须 serverEnforced,并采用 deny 或 require-server 失败策略;风险输入如果进入 VMP,必须映射到非 SDK 胶水的本地决策函数。条件 priority 要唯一且连续,避免审计清单与控制面顺序不一致。顶层生命周期策略固定表达 fetch 失败保留上次激活值、首次无值使用默认值、activate 失败保留当前值。项目可扩展这些规则,但不得静默接受未知枚举。

脚本通过只证明策略文件满足当前静态约束。它不验证 Firebase 控制台真实模板,不证明客户端已取得参数,也不证明 VMP 转换后的候选运行正确。实际门禁还要把策略摘要与候选摘要、Remote Config 模板版本和测试回执绑定,并对每个决策函数执行固定输入与失败路径回归。

校验 Remote Config 生命周期与 VMP 决策映射
from pathlib import Path
import json
import re
import sys

if len(sys.argv) != 2:
    raise SystemExit(2)
policy_path = Path(sys.argv[1])
if not policy_path.is_file():
    raise SystemExit(2)
try:
    policy = json.loads(policy_path.read_text(encoding="utf-8"))
except (OSError, UnicodeError, json.JSONDecodeError):
    raise SystemExit(2)
required_lifecycle = {
    "fetchFailure": "keep-last-activated",
    "noActivatedValue": "use-defaults",
    "activateFailure": "keep-current"
}
if policy.get("lifecycle") != required_lifecycle:
    raise SystemExit(3)
parameters = policy.get("parameters")
if not isinstance(parameters, list) or not parameters:
    raise SystemExit(2)
allowed_classes = {"presentation", "risk-input", "authorization", "disable-only"}
allowed_failures = {"use-default", "deny", "require-server"}
allowed_scopes = {"protect", "exclude"}
key_pattern = re.compile(r"^[a-z][a-zA-Z0-9_]{2,63}$")
glue_pattern = re.compile(r"(?i)(fetch|activate|remoteConfig|firebase|callback|listener)")
issues = []
seen_keys = set()
for item in parameters:
    if not isinstance(item, dict):
        raise SystemExit(2)
    key = item.get("key")
    if not isinstance(key, str) or not key_pattern.fullmatch(key) or key in seen_keys:
        issues.append({"key": str(key), "issue": "invalid-or-duplicate-key"})
        continue
    seen_keys.add(key)
    decision_class = item.get("decisionClass")
    failure_policy = item.get("failurePolicy")
    vmp_scope = item.get("vmpScope")
    function_name = item.get("decisionFunction")
    if decision_class not in allowed_classes or failure_policy not in allowed_failures or vmp_scope not in allowed_scopes:
        issues.append({"key": key, "issue": "unknown-policy-enum"})
    if not isinstance(function_name, str) or not function_name.strip():
        issues.append({"key": key, "issue": "missing-decision-function"})
    if vmp_scope == "protect" and glue_pattern.search(function_name or ""):
        issues.append({"key": key, "issue": "sdk-glue-selected-for-vmp"})
    conditions = item.get("conditions", [])
    priorities = [condition.get("priority") for condition in conditions if isinstance(condition, dict)]
    if len(priorities) != len(conditions) or priorities != list(range(len(priorities))):
        issues.append({"key": key, "issue": "condition-priority-gap"})
    if decision_class == "authorization":
        if item.get("default") is not False:
            issues.append({"key": key, "issue": "authorization-default-not-deny"})
        if item.get("serverEnforced") is not True:
            issues.append({"key": key, "issue": "authorization-not-server-enforced"})
        if failure_policy not in {"deny", "require-server"}:
            issues.append({"key": key, "issue": "unsafe-authorization-fallback"})
report = {"variant": policy.get("variant"), "parameterCount": len(parameters), "issues": issues}
print(json.dumps(report, ensure_ascii=False, indent=2))
if issues:
    raise SystemExit(3)

回归矩阵必须覆盖配置与候选组合

静态清单通过后,测试应按参数来源和生命周期展开。至少覆盖应用默认值、旧激活值、新 fetch 未 activate、新激活值、fetch 失败、activate 失败、类型错误和条件冲突。每个场景固定配置版本、应用候选、构建变体、用户属性和预期原因码。只测 happy path 会遗漏首次安装、离线和陈旧缓存,而这些恰好是安全默认值真正生效的地方。

VMP 保护前后应使用同一输入快照比较决策结果,重点观察布尔值、阈值边界、状态迁移、异常语义和线程行为。测试通过要绑定同一候选摘要,不能用未保护构建替代。若保护后的决策函数无法诊断,应保留外层原因码和安全日志,但不要记录远端用户属性、认证令牌或完整业务对象。可观测性服务于定位,不应扩大数据暴露。

变体矩阵也不能省略。release、地区和渠道变体可能带不同默认资源、applicationId、依赖和签名配置。一个 variant 通过不代表其他 variant 可以继承结论。若同一 Remote Config 项目服务多个变体,应验证条件能准确区分目标应用,并为错误项目、错误包名和未知版本设置安全兜底。

发布前最小回归矩阵
场景输入状态关键断言失败后决定
首次启动只有应用默认值敏感动作不被默认放宽修正默认值后重建
fetch 未 activate新缓存加旧当前值继续使用一致旧快照修正状态观测
fetch 失败旧缓存或无缓存执行对应陈旧与失败策略阻断开放型回退
activate 失败当前值保持不变不产生混合参数集修正读取快照
多条件命中固定版本、地区和属性按登记优先级选择校正条件顺序
服务端拒绝客户端配置显示允许高价值动作仍被服务端拒绝阻断客户端单边授权

发布材料要明确边界与下一动作

一份可执行的评审材料应包含最终候选摘要、variant、默认值清单、Remote Config 模板版本、条件顺序、参数分类、失败策略、本地决策函数、VMP 方法清单和服务端裁决接口。每项参数说明它可以放宽还是只能收紧、离线时使用什么值、何时要求服务端,以及哪个测试回执覆盖它。这样审查者能沿责任链判断,而不是只看到一串 key。

OWASP MASVS-RESILIENCE 将移动端抗逆向与抗篡改视为纵深防御控制。这个定位适用于本场景:VMP 可以提高本地风险决策被修改的成本,但不能改变客户端是公共环境的事实,也不证明任何候选达到特定防护强度。没有同候选的变换、构建和运行回执时,应记录待验证,不写性能数字、阻断结论或客户效果。

准备接入评估时,可在御盾中央平台提交候选、默认值与模板摘要、条件表、失败策略、服务端授权边界、决策函数清单和回归矩阵。登录、注册、申请、价格与控制台动作统一由中央平台承接。评审重点是先把授权留在可信侧,再选择值得保护且能够验证的本地核心,而不是把所有 Remote Config 调用一起纳入 VMP。

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
Android 客户端需要提供应用内默认值,并按 fetch 与 activate 生命周期采用 Remote Config 参数。Firebase Remote Config for Android 描述默认值、获取、激活和客户端读取流程。Remote Config 不是秘密存储或强制授权,客户端最终能观察其采用的参数。
Remote Config 条件可以使用应用版本、平台、国家地区、用户属性和随机百分位等输入。Firebase Remote Config conditions 描述可用条件、参数值选择与条件优先级。条件命中不证明客户端已经拉取或激活新值,也不能替代服务端拒绝危险操作。
Remote Config rollout 可以向部分目标用户逐步开放参数并根据监控调整或回滚。Firebase Remote Config rollouts 描述 rollout 的目标、监控和参数回滚机制。rollout 不保证离线客户端立即收到新值,也不能恢复已经损坏的本地状态。
Remote Config 可用于按版本或用户群控制功能暴露等场景。Firebase Remote Config use cases 描述版本控制、用户群和功能发布相关用法。用例不证明安全关键默认值、紧急停用时效、加固兼容或当前项目效果。
build type、product flavor 和 source set 会组合成不同 Android 构建变体。Android build variants 描述变体、applicationId、资源与签名配置的组合。同一仓库不能证明所有 variant 具有相同默认值、证书和运行行为。
移动端抗逆向与抗篡改属于纵深防御控制。OWASP MASVS-RESILIENCE 给出移动应用韧性控制范围。控制目录不证明具体候选达到某种保护强度,也不替代服务端授权和完整发布链。
Remote Config 授权类参数必须失败关闭并由服务端最终强制执行。工程判断:客户端参数、缓存和分支都位于公共设备,不能成为高价值操作的唯一可信根。具体服务端授权模型需要根据主体、对象、动作和业务数据另行设计与验证。
VMP 应优先保护规范化参数快照后的稳定本地决策核心。工程判断:把 fetch、activate、SDK 回调和易变胶水纳入会扩大兼容与诊断成本,却不改变授权边界。是否适合保护仍需结合真实候选、方法清单、回归证据和失败语义评估。

工程常见问题

把 Remote Config 的授权判断做 VMP 后能否替代服务端权限检查?

不能。VMP 只能提高本地分支被分析和修改的成本,Remote Config 参数仍由公共客户端采用。高价值操作必须由服务端按主体、对象和动作重新授权。

fetch 成功是否表示新参数已经生效?

不表示。fetch 通常只取得并缓存参数,activate 才切换当前值。监控和测试要分别记录 fetch、activate、值来源与配置版本。

紧急 kill switch 为什么不能只放在 Remote Config?

离线、缓存、节流和未激活状态都会产生传播延迟。客户端参数可以收敛能力,但关键服务还应能独立拒绝危险动作,并准备状态恢复方案。

哪些 Remote Config 相关方法适合进入 VMP?

优先考虑接收规范化参数快照和业务状态的稳定决策函数。SDK fetch、activate、回调、序列化、资源读取和生成胶水通常排除,以控制兼容与诊断成本。

一个 release 变体测试通过后能否覆盖其他渠道?

不能直接继承。build type、flavor、source set、applicationId、资源、依赖和签名配置可能不同,每个交付 variant 都要核对最终默认值与真实候选。

申请 Remote Config 与 VMP 边界评估需要什么材料?

准备最终候选、variant、默认值与模板摘要、条件优先级、失败策略、服务端授权边界、决策函数清单、VMP 方法清单和回归矩阵,再通过御盾中央平台提交。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: VMP 加固是什么,如何选择保护范围