skills/check-test-code-quality/rules/R012/SKILL.md
# R012: 签名证书APL等级和app-feature配置错误 ## 规则信息 | 属性 | 值 | |------|-----| | 规则编号 | R012 | | 问题类型 | 签名证书APL等级和app-feature配置错误 | | 严重级别 | Critical | | 规则复杂度 | simple | | 扫描范围 | 所有.p7b文件(signature/*.p7b 和 根目录*.p7b) | | testcase字段 | `-`(p7b为非测试文件,无对应it()块) | ## 问题描述 签名证书p7b文件中使用了`system_core`等级或`app-feature`字段配置错误。 - **规范要求**: - `apl`字段:控制应用等级,默认普通应用配置为`normal`,禁止使用`system_core` - `app-feature`字段:控制普通应用还是系统应用,开源仓默认为`hos_normal_app` - 极少数情况可以使用`system_basic`(仅限于涉及特定受限权限) - 高于APL等级的权限依据"权限ACL是否
npx skillsauth add openharmonyinsight/openharmony-skills skills/check-test-code-quality/rules/R012Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| 属性 | 值 |
|------|-----|
| 规则编号 | R012 |
| 问题类型 | 签名证书APL等级和app-feature配置错误 |
| 严重级别 | Critical |
| 规则复杂度 | simple |
| 扫描范围 | 所有.p7b文件(signature/.p7b 和 根目录.p7b) |
| testcase字段 | -(p7b为非测试文件,无对应it()块) |
签名证书p7b文件中使用了system_core等级或app-feature字段配置错误。
apl字段:控制应用等级,默认普通应用配置为normal,禁止使用system_coreapp-feature字段:控制普通应用还是系统应用,开源仓默认为hos_normal_appsystem_basic(仅限于涉及特定受限权限)使用normal等级,app-feature配置为hos_normal_app。修复时必须使用签名工具重新生成p7b文件(直接修改JSON会导致签名失效)。
扫描R012时,必须提取p7b文件中的acls.allowed-acls和permissions.restricted-permissions字段,对涉及权限进行A-H类分类。分类结果写入Excel"修复建议"列。
| 权限类型 | 文档来源 | 正确apl | 正确app-feature | 是否需确认 | 说明 | |---------|---------|--------|---------------|-----------|------| | A类 | permissions-for-all-user.md | normal | hos_normal_app | 否 | 用户授权开放权限 | | B类 | permissions-for-all.md | normal | hos_normal_app | 否 | 系统授权开放权限 | | C类 | restricted-permissions.md | system_basic | hos_normal_app | 否 | 受限权限(通过ACL申请) | | D类 | permissions-for-enterprise-apps.md | system_basic | hos_system_app | 是 | 企业应用权限 | | E类 | permissions-for-mdm-apps.md | system_basic | hos_system_app | 是 | MDM应用权限 | | F类 | permissions-for-system-apps-no-acl.md | system_basic | hos_system_app | 是 | 系统应用权限(无ACL),不推荐 | | G类 | permissions-for-system-apps-user.md | system_basic | hos_system_app | 是 | 系统应用权限(用户授权),不推荐 | | H类 | permissions-for-system-apps.md | system_basic | hos_system_app | 是 | 系统应用权限,不推荐 | | 未知 | 不在上述文档中 | 需确认 | 需确认 | 是 | 新增或自定义权限 |
当前: apl=system_core, app-feature=hos_normal_app。涉及权限: A类(ohos.permission.CAMERA), B类(ohos.permission.INTERNET)。
建议: 可自动修复。将apl改为normal, app-feature保持hos_normal_app。保留acls字段。
当前: apl=system_core, app-feature=hos_normal_app。涉及权限: C类(ohos.permission.SYSTEM_FLOAT_WINDOW)。
建议: 可自动修复。将apl改为system_basic, app-feature保持hos_normal_app。必须保留acls字段: ["ohos.permission.SYSTEM_FLOAT_WINDOW"]。
当前: apl=system_core, app-feature=hos_system_app。涉及权限: D类(ohos.permission.GET_BUNDLE_INFO_PRIVILEGED)。
建议: 【需用户确认】检测到企业应用权限(D类)。如确认为企业应用: apl=system_basic, app-feature=hos_system_app; 如为普通应用: 移除该权限后 apl=normal, app-feature=hos_normal_app。
当前: apl=system_core, app-feature=hos_system_app。涉及权限: H类(ohos.permission.INSTALL_BUNDLE)。
建议: 【需用户确认】检测到系统应用权限(H类),开源仓不推荐使用。建议移除该权限后 apl=normal, app-feature=hos_normal_app; 如必须使用,需系统签名,apl=system_basic, app-feature=hos_system_app。
当前: apl=system_core, app-feature=hos_normal_app。涉及权限: 未知(ohos.permission.CUSTOM_XXX)。
建议: 【需用户确认】检测到未知权限,不在已知权限列表中。请查阅官方文档确认权限级别。默认保守策略: apl=normal, app-feature=hos_normal_app。
当前: apl=system_core, app-feature=hos_system_app。涉及权限: A类(ohos.permission.INTERNET), C类(ohos.permission.SYSTEM_FLOAT_WINDOW), H类(ohos.permission.INSTALL_BUNDLE)。
建议: 【需用户确认】检测到系统应用权限(H类): ohos.permission.INSTALL_BUNDLE。建议移除H类权限后,按剩余权限确定配置: C类存在需apl=system_basic, app-feature=hos_normal_app。必须保留acls字段。
import os
import subprocess
import json
import re
def find_p7b_files(scan_root):
p7b_files = []
for dirpath, dirnames, filenames in os.walk(scan_root):
for fn in filenames:
if fn.endswith('.p7b'):
p7b_files.append(os.path.join(dirpath, fn))
return p7b_files
p7b文件是PKCS#7签名格式,需要使用openssl提取其中的JSON数据。
def extract_p7b_json(p7b_path):
try:
result = subprocess.run(
['openssl', 'cms', '-verify', '-in', p7b_path,
'-inform', 'DER', '-noverify'],
capture_output=True, text=True, timeout=10
)
if result.returncode == 0 and result.stdout:
return json.loads(result.stdout)
except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
pass
try:
with open(p7b_path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='ignore')
json_match = re.search(r'\{.*\}', text, re.DOTALL)
if json_match:
return json.loads(json_match.group())
except (json.JSONDecodeError, Exception):
pass
return None
def classify_permissions(config, permission_db):
acls = []
restricted = []
acl_section = config.get('acls', {}).get('allowed-acls', [])
if isinstance(acl_section, list):
acls = acl_section
perm_section = config.get('permissions', {}).get('restricted-permissions', [])
if isinstance(perm_section, list):
restricted = perm_section
all_permissions = set(acls + restricted)
classified = {}
for perm in all_permissions:
category = permission_db.get(perm, 'unknown')
if category not in classified:
classified[category] = []
classified[category].append(perm)
return classified
def generate_suggestion(config, classified):
bundle_info = config.get('bundle-info', {})
current_apl = bundle_info.get('apl', 'unknown')
current_app_feature = bundle_info.get('app-feature', 'unknown')
all_perms_desc = []
for cat in sorted(classified.keys()):
perms = classified[cat]
all_perms_desc.append(f"{cat}类({', '.join(perms)})")
perm_summary = ', '.join(all_perms_desc) if all_perms_desc else '无'
has_system_core = current_apl == 'system_core'
has_wrong_feature = current_app_feature != 'hos_normal_app'
if not has_system_core and not has_wrong_feature:
return None
has_confirmed_types = any(c in classified for c in ['D', 'E', 'F', 'G', 'H', 'unknown'])
has_c_type = 'C' in classified
if has_confirmed_types:
confirmed_types = [c for c in ['D', 'E', 'F', 'G', 'H', 'unknown'] if c in classified]
confirmed_perms = []
for ct in confirmed_types:
confirmed_perms.extend(classified[ct])
return (
f"当前: apl={current_apl}, app-feature={current_app_feature}。"
f"涉及权限: {perm_summary}。\n"
f"建议: 【需用户确认】检测到需确认的权限({', '.join(confirmed_types)}类): "
f"{', '.join(confirmed_perms)}。"
f"请查阅官方文档确认权限级别后修改配置。"
)
if has_c_type:
acls = config.get('acls', {}).get('allowed-acls', [])
acls_str = json.dumps(acls, ensure_ascii=False)
return (
f"当前: apl={current_apl}, app-feature={current_app_feature}。"
f"涉及权限: {perm_summary}。\n"
f"建议: 可自动修复。将apl改为system_basic, app-feature保持hos_normal_app。"
f"必须保留acls字段: {acls_str}。"
)
return (
f"当前: apl={current_apl}, app-feature={current_app_feature}。"
f"涉及权限: {perm_summary}。\n"
f"建议: 可自动修复。将apl改为normal, app-feature保持hos_normal_app。保留acls字段。"
)
def scan_r012(scan_root, base_dir, permission_db):
issues = []
p7b_files = find_p7b_files(scan_root)
for p7b_path in p7b_files:
config = extract_p7b_json(p7b_path)
if not config:
continue
bundle_info = config.get('bundle-info', {})
current_apl = bundle_info.get('apl', '')
current_app_feature = bundle_info.get('app-feature', '')
has_problem = (current_apl == 'system_core') or (current_app_feature != 'hos_normal_app')
if not has_problem:
continue
classified = classify_permissions(config, permission_db)
suggestion = generate_suggestion(config, classified)
if not suggestion:
continue
rel_path = os.path.relpath(p7b_path, base_dir)
issues.append({
'rule': 'R012',
'type': '签名证书APL等级和app-feature配置错误',
'severity': 'Critical',
'file': rel_path,
'line': 1,
'testcase': '-',
'snippet': f'apl={current_apl}, app-feature={current_app_feature}',
'suggestion': suggestion,
})
return issues
// 错误1:apl字段使用system_core
{
"bundle-info": {
"apl": "system_core",
"app-feature": "hos_normal_app"
}
}
// 错误2:app-feature字段不是hos_normal_app
{
"bundle-info": {
"apl": "normal",
"app-feature": "hos_system_app"
}
}
// 正确:使用normal等级和hos_normal_app
{
"bundle-info": {
"apl": "normal",
"app-feature": "hos_normal_app"
}
}
// 正确:特殊情况使用system_basic(仅限C类受限权限)
{
"bundle-info": {
"apl": "system_basic",
"app-feature": "hos_normal_app"
},
"acls": {
"allowed-acls": ["ohos.permission.MANAGE_BLUETOOTH"]
}
}
acls、permissions、distribution-certificate等所有字段严重性: 极严重,导致R012规则完全失效(100%漏检)
p7b签名文件是DER(ASN.1)二进制格式,文件头为0x30 0x82。必须用raw.decode('utf-8', errors='replace')容错解码后用正则提取"apl"、"app-feature"等字段,不能使用json.loads()。
正确做法:
def extract_p7b_fields(p7b_path):
with open(p7b_path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='replace')
m = re.search(r'"apl"\s*:\s*"([^"]*)"', text)
apl = m.group(1) if m else ''
m = re.search(r'"app-feature"\s*:\s*"([^"]*)"', text)
app_feature = m.group(1) if m else ''
return apl, app_feature
详见 references/TRAPS.md 陷阱1c。
每条issue的字段:
| 字段 | 值 |
|------|-----|
| rule | R012 |
| type | 签名证书APL等级和app-feature配置错误 |
| severity | Critical |
| file | 相对路径(如xxx/signature/openharmony_sx.p7b) |
| line | 1 |
| testcase | - |
| snippet | apl=system_core, app-feature=hos_normal_app |
| suggestion | 包含权限分类和具体修复建议(见6个场景格式) |
{
"profile": {
"apl": "normal",
"app-feature": ""
}
}
说明: app-feature字段缺失或为空同样视为配置错误,开源仓默认应为hos_normal_app。
testing
--- name: ohos-req-value-decision description: Use after review meeting to record decision and route to next step. Triggers: 评审决策纪要, 评审结论回流, value decision, 评审接纳, 评审不接纳, 评审退回, 下次重新上会. Do NOT use for feature baseline (ohos-req-feature-baseline), review gate checks (ohos-req-review-gate), or IR generation (ohos-req-feature-to-ir). metadata: author: openharmony scope: common stage: requirements capability: value-decision version: 0.3.0 status: draft tags: - sdd - requirements
development
Use when converting an OpenHarmony requirement document, spec, or design proposal into an OpenHarmony review slide deck (需求评审 / 需求变更评审 / 设计评审 PPTX) — produces the fixed OpenHarmony-branded review-deck structure (OH logo on every page) with architecture/flow diagrams and field tables. Triggers on "需求评审PPT", "需求变更评审", "把需求文档转成评审PPT", "spec转评审PPT", "requirement/spec to review deck". NOT for arbitrary or generic slide decks unrelated to OpenHarmony requirement/design review.
testing
Use when performing the Phase 0 Step 0.5 Review Ready Gate on a 04-feature.md, especially when the user says "evaluate gate", "review readiness", "feature ready?", "should we generate IR", or when the ohos-req-intake-orchestration main session needs a structured Ready / Conditional Ready / Not Ready judgment instead of doing the check inline. Reads 01-04, runs seven fixed checks plus a conditional-items check, and returns a machine-readable JSON summary plus a human-readable table that the main session can route on. Do NOT use for feature baseline generation (ohos-req-feature-baseline), value decision recording (ohos-req-value-decision), or IR generation (ohos-req-feature-to-ir).
testing
--- name: ohos-req-requirement-intake description: Use when importing an OHOS requirement into Phase 0.1, especially for 01-requirement.md, requirement intake, background, user value, scenarios, scope, FR/NFR, affected modules, or priority. Triggers: 需求导入, 01-requirement, 需求基线, RR单号. Do NOT use for feasibility analysis (ohos-req-feasibility-analysis), architecture decision (ohos-req-arch-decision), or feature baseline (ohos-req-feature-baseline). metadata: author: openharmony scope: common