@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/package.json +1 -1
  2. package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
  3. package/skills/cosmic-build/SKILL.md +24 -9
  4. package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
  5. package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
  6. package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
  7. package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
  8. package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
  9. package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
  10. package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
  11. package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
  12. package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
  13. package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
  14. package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
  15. package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
  16. package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
  17. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
  18. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
  19. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
  20. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
  21. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
  22. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
  23. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
  24. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
  25. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
  26. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
  27. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
  28. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
  29. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
  30. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
  31. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
  32. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
  33. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
  34. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
  35. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
  36. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
  37. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
  38. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
  39. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
  40. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
  41. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
  42. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
  43. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
  44. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
  45. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
  46. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
  47. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
  48. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
  49. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
  50. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
  51. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
  52. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
  53. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
  54. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
  55. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
  56. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
  57. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
  58. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
  59. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
  60. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
  61. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
  62. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
  63. package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
  64. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
  65. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
  66. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
  67. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
  68. package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
  69. package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
  70. package/skills/cosmic-build/references/compile-guide.md +36 -1
  71. package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
  72. package/skills/cosmic-build/references/delivery.md +16 -2
  73. package/skills/cosmic-build/references/execution-contract.md +15 -3
  74. package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
  75. package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
  76. package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
  77. package/skills/cosmic-build/references/report-filter-contract.md +53 -15
  78. package/skills/cosmic-build/references/sdk-inspection.md +4 -1
  79. package/skills/cosmic-build/references/type-registry.json +26 -17
  80. package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
  81. package/skills/cosmic-build/scripts/_http_client.py +3 -0
  82. package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
  83. package/skills/cosmic-build/scripts/configure_list.py +112 -0
  84. package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
  85. package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
  86. package/skills/cosmic-build/scripts/execution_binding.py +25 -0
  87. package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
  88. package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
  89. package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
  90. package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
  91. package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
  92. package/skills/cosmic-build/scripts/register_plugins.py +27 -10
  93. package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
  94. package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
  95. package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
  96. package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
  97. package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
  98. package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
  99. package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
  100. package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
  101. package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
  102. package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
  103. package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
  104. package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
  105. package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
  106. package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
  107. package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
  108. package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
  109. package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
  110. package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
  111. package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
  112. package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
  113. package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
  114. package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
  115. package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
  116. package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
  117. package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
  118. package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
  119. package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
  120. package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
  121. package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
  122. package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
  123. package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
  124. package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
  125. package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
  126. package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
  127. package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
  128. package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
  129. package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
  130. package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
  131. package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
  132. package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
  133. package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
  134. package/skills/cosmic-build/scripts/test_environment.py +67 -1
  135. package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
  136. package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
  137. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
  138. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
  139. package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
  140. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
  141. package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
  142. package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
  143. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
  144. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
  145. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
  146. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
  147. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
  148. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
  149. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
  150. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
  151. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
  152. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
  153. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
  154. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
  155. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
  156. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
  157. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
  158. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
  159. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
  160. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
  161. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
  162. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
  163. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
  164. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
  165. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
  166. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
  167. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
  168. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
  169. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
  170. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
  171. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
  172. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
  173. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
  174. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
  175. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
  176. package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
  177. package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
  178. package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
  179. package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
  180. package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
  181. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
  182. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
  183. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
  184. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
  185. package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
  186. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
  187. package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
  188. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
  189. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
  190. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
  191. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
  192. package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
  193. package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
  194. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
  195. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
  196. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
  197. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
  198. package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
  199. package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
  200. package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
  201. package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
  202. package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
  203. package/skills/cosmic-dev-best-practices/references/README.md +2 -2
  204. package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
  205. package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
  206. package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
  207. package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
  208. package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
  209. package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
  210. package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
  211. package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
  212. package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
  213. package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
  214. package/skills/erp-analysis/references/delivery-contract.md +2 -1
  215. package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
  216. package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
  217. package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
  218. package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
@@ -0,0 +1,901 @@
1
+ import hashlib
2
+ import json
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+
10
+ SCRIPTS = Path(__file__).resolve().parents[1]
11
+ sys.path.insert(0, str(SCRIPTS))
12
+
13
+ import dcs_code_gate # noqa: E402
14
+ from dcs_code_gate import RULES, dcs_rule_issues, dcs_rule_warnings, scan_source, scan_sql # noqa: E402
15
+
16
+
17
+ _GOOD = '''package cus.demo.formplugin;
18
+
19
+ import java.util.EventObject;
20
+
21
+ import kd.bos.dataentity.entity.DynamicObject;
22
+ import kd.bos.entity.datamodel.events.ChangeData;
23
+ import kd.bos.entity.datamodel.events.PropertyChangedArgs;
24
+ import kd.bos.exception.KDBizException;
25
+ import kd.bos.form.plugin.AbstractFormPlugin;
26
+ import kd.bos.logging.Log;
27
+ import kd.bos.logging.LogFactory;
28
+ import kd.bos.orm.query.QCP;
29
+ import kd.bos.orm.query.QFilter;
30
+ import kd.bos.servicehelper.BusinessDataServiceHelper;
31
+
32
+ /**
33
+ * Fills the unit price when the material changes.
34
+ */
35
+ public class MaterialPricePlugin extends AbstractFormPlugin {
36
+ private static final Log LOG = LogFactory.getLog(MaterialPricePlugin.class);
37
+ private static final String FIELD_MATERIAL = "material";
38
+ private static final String FIELD_PRICE = "price";
39
+
40
+ @Override
41
+ public void afterCreateNewData(EventObject e) {
42
+ super.afterCreateNewData(e);
43
+ getModel().setValue(FIELD_PRICE, 0);
44
+ }
45
+
46
+ @Override
47
+ public void propertyChanged(PropertyChangedArgs e) {
48
+ super.propertyChanged(e);
49
+ if (!FIELD_MATERIAL.equals(e.getProperty().getName())) {
50
+ return;
51
+ }
52
+ ChangeData[] changes = e.getChangeSet();
53
+ Object value = changes[0].getNewValue();
54
+ if (!(value instanceof DynamicObject)) {
55
+ return;
56
+ }
57
+ DynamicObject material = (DynamicObject) value;
58
+ QFilter filter = new QFilter("id", QCP.equals, material.getPkValue());
59
+ DynamicObject loaded = BusinessDataServiceHelper.loadSingle("bd_material", "id,price", new QFilter[]{filter});
60
+ if (loaded == null) {
61
+ LOG.warn("material not found");
62
+ throw new KDBizException("物料不存在");
63
+ }
64
+ getModel().setValue(FIELD_PRICE, loaded.getBigDecimal(FIELD_PRICE));
65
+ getView().updateView(FIELD_PRICE);
66
+ }
67
+ }
68
+ '''
69
+
70
+ _BAD = '''package kd.cus.demo;
71
+
72
+ import java.util.HashMap;
73
+ import java.util.Map;
74
+ import java.util.List;
75
+ import kd.bos.orm.ORM;
76
+ import kd.bos.db.DB;
77
+ import kd.bos.logging.Log;
78
+ import kd.bos.logging.LogFactory;
79
+ import kd.bos.servicehelper.operation.SaveServiceHelper;
80
+ import kd.bos.servicehelper.QueryServiceHelper;
81
+
82
+ public class Bad extends AbstractFormPlugin {
83
+ private static final Log LOG = LogFactory.getLog(Bad.class);
84
+ private static final Map<String, String> CACHE = new HashMap<>();
85
+
86
+ @Override
87
+ public void propertyChanged(PropertyChangedArgs e) {
88
+ getView().updateView();
89
+ }
90
+
91
+ public void doWork(List<DynamicObject> rows) {
92
+ for (DynamicObject row : rows) {
93
+ LOG.info("row " + row.getPkValue());
94
+ SaveServiceHelper.save(new DynamicObject[]{row});
95
+ for (DynamicObject inner : rows) {
96
+ QueryServiceHelper.query("bd_material", "id", null);
97
+ }
98
+ }
99
+ System.out.println("done");
100
+ try {
101
+ ORM.create().queryDataSet("x", "bd_material", "id", null);
102
+ } catch (Exception ex) {
103
+ ex.printStackTrace();
104
+ }
105
+ String a = row1("billno");
106
+ a = row1("billno") + row1("billno");
107
+ if (a.size() == 0) {
108
+ throw new RuntimeException("x");
109
+ }
110
+ }
111
+
112
+ private String row1(String key) { return key; }
113
+ }
114
+ '''
115
+
116
+
117
+ def _codes(issues):
118
+ return {code for issue in issues for code in issue["ruleCodes"]}
119
+
120
+
121
+ def _plugin(body, imports=""):
122
+ """A minimal, otherwise clean plugin class around ``body``."""
123
+ return f"package cus.demo;\n{imports}\npublic class Demo {{\n{body}\n}}\n"
124
+
125
+
126
+ def test_every_reported_rule_code_exists_in_the_dcs_rule_table():
127
+ issues = scan_source(_BAD) + scan_source(_GOOD)
128
+ assert _codes(issues) <= set(RULES)
129
+ # Family rules carry one code per keyword in the DCS table; the family code itself never matches.
130
+ assert "CVE-KD-0056" not in RULES and "CVE-KD-0056-05" in RULES
131
+ assert "CVE-KD-0026" not in RULES and "CVE-KD-0026-25" in RULES
132
+ assert "CVE-KD-0721" not in RULES and {"CVE-KD-0721-1", "CVE-KD-0721-2"} <= set(RULES)
133
+
134
+
135
+ def test_the_violations_dcs_rejects_are_blocked_together():
136
+ blocked = dcs_rule_issues(_BAD)
137
+
138
+ assert {
139
+ "CVE-KD-2026-0508", # kd.bos.orm.ORM
140
+ "CVE-KD-0413", # kd.bos.db.DB
141
+ "CVE-KD-0056-05", # static HashMap
142
+ "CVE-KD-0058-1", # 循环内日志
143
+ "CVE-KD-0051-10", # 循环内保存
144
+ "CVE-KD-1215-1", # 嵌套循环查询
145
+ "CVE-KD-0303", # query with a null filter
146
+ "CVE-KD-2026-0618", # unconditional updateView in propertyChanged
147
+ "java:S106", # System.out
148
+ "java:S1155", # size() == 0
149
+ "java:S112", # generic RuntimeException
150
+ "java:S1128", # unused import
151
+ "java:S1192", # duplicated literal
152
+ } <= _codes(blocked)
153
+ assert all(issue["level"] == "block" and issue["fix"] and issue["line"] > 0 for issue in blocked)
154
+
155
+
156
+ def test_a_plugin_written_to_the_rules_passes(tmp_path):
157
+ source = tmp_path / "src" / "main" / "java" / "cus" / "demo" / "formplugin" / "MaterialPricePlugin.java"
158
+
159
+ assert scan_source(_GOOD, source) == []
160
+
161
+
162
+ def test_the_package_must_match_the_directory_only_when_the_path_is_known(tmp_path):
163
+ assert "java:S1598" in _codes(scan_source(_GOOD, tmp_path / "Other.java"))
164
+ assert "java:S1598" not in _codes(scan_source(_GOOD))
165
+ source = tmp_path / "src" / "cus" / "demo" / "formplugin" / "unused" / ".." / "MaterialPricePlugin.java"
166
+ assert "java:S1598" not in _codes(scan_source(_GOOD, source))
167
+
168
+
169
+ def test_filter_null_is_found_in_whichever_position_the_overload_puts_it():
170
+ for call in (
171
+ 'QueryServiceHelper.query("bd_material", "id", null)',
172
+ 'QueryServiceHelper.queryDataSet("algo", "bd_material", "id", null, "id")',
173
+ 'BusinessDataServiceHelper.loadFromCache(type, (QFilter[]) null)',
174
+ 'BusinessDataServiceHelper.load("bd_material", "id", new QFilter[0])',
175
+ ):
176
+ source = _plugin(f" public Object run(DynamicObjectType type) {{\n return {call};\n }}")
177
+ assert "CVE-KD-0303" in _codes(dcs_rule_issues(source)), call
178
+ source = _plugin(' public Object run(QFilter[] filters) {\n'
179
+ ' return QueryServiceHelper.queryDataSet("algo", "bd_material", "id", filters, null);\n }')
180
+ assert "CVE-KD-0303" not in _codes(dcs_rule_issues(source))
181
+
182
+
183
+ @pytest.mark.parametrize("call", [
184
+ 'QueryServiceHelper.query(entity, fields, filters)',
185
+ 'QueryServiceHelper.query(entity, fields, filters, null)',
186
+ 'QueryServiceHelper.query(entity, fields, filters, null, top)',
187
+ 'QueryServiceHelper.query("algo", entity, fields, filters, null)',
188
+ 'QueryServiceHelper.query("algo", entity, fields, filters, null, top)',
189
+ 'QueryServiceHelper.queryDataSet("algo", entity, fields, filters, null)',
190
+ 'QueryServiceHelper.queryDataSet("algo", entity, fields, filters, null, top)',
191
+ 'QueryServiceHelper.queryOne(entity, fields, filters)',
192
+ 'QueryServiceHelper.exists(entity, filters)',
193
+ 'QueryServiceHelper.queryPrimaryKeys(entity, filters, null, top)',
194
+ 'QueryServiceHelper.queryPrimaryKeys("algo", entity, filters, null, top)',
195
+ 'QueryServiceHelper.queryPrimaryKeys("algo", entityType, filters, null, top)',
196
+ 'BusinessDataServiceHelper.load(entity, fields, filters)',
197
+ 'BusinessDataServiceHelper.load(entity, fields, filters, null)',
198
+ 'BusinessDataServiceHelper.load(entity, fields, filters, null, top)',
199
+ 'BusinessDataServiceHelper.load(entity, fields, filters, null, top, top)',
200
+ 'BusinessDataServiceHelper.loadFromCache(entity, fields, filters)',
201
+ 'BusinessDataServiceHelper.loadFromCache(entity, fields, filters, null)',
202
+ 'BusinessDataServiceHelper.loadFromCache(type, filters)',
203
+ 'BusinessDataServiceHelper.loadSingle(entity, (QFilter[]) filters)',
204
+ 'BusinessDataServiceHelper.loadSingle(entity, fields, filters)',
205
+ 'BusinessDataServiceHelper.loadSingleFromCache(entity, fields, filters)',
206
+ ])
207
+ def test_each_filter_overload_accepts_filters_and_rejects_explicit_empty_filters(call):
208
+ def source(expression):
209
+ return _plugin(' public Object run(String entity, String fields, QFilter[] filters, int top, '
210
+ 'DynamicObjectType type, IDataEntityType entityType) {\n'
211
+ f' return {expression};\n }}')
212
+
213
+ assert "CVE-KD-0303" not in _codes(scan_source(source(call)))
214
+ for empty in ("(QFilter[]) null", "new QFilter[0]", "new QFilter[]{}"):
215
+ assert "CVE-KD-0303" in _codes(dcs_rule_issues(source(call.replace("(QFilter[]) filters", "filters").replace("filters", empty))))
216
+
217
+
218
+ @pytest.mark.parametrize("call", [
219
+ 'QueryServiceHelper.exists(entity, pk)',
220
+ 'QueryServiceHelper.exists(entity, (Object) null)',
221
+ 'BusinessDataServiceHelper.load(ids, type)',
222
+ 'BusinessDataServiceHelper.loadFromCache(ids, type)',
223
+ 'BusinessDataServiceHelper.loadSingle(pk, type)',
224
+ 'BusinessDataServiceHelper.loadSingle(pk, type, 0, 1)',
225
+ 'BusinessDataServiceHelper.loadSingle(pk, entity)',
226
+ 'BusinessDataServiceHelper.loadSingle(pk, entity, null)',
227
+ 'BusinessDataServiceHelper.loadSingleFromCache(pk, entity, null)',
228
+ 'BusinessDataServiceHelper.loadSingleFromCache(pk, type)',
229
+ ])
230
+ def test_primary_key_overloads_are_not_treated_as_unfiltered_queries(call):
231
+ source = _plugin(' public Object run(Object pk, Object[] ids, String entity, DynamicObjectType type) {\n'
232
+ f' return {call};\n }}')
233
+ assert "CVE-KD-0303" not in _codes(scan_source(source))
234
+
235
+
236
+ def test_unresolved_overloads_are_advisory_instead_of_blocking_valid_code():
237
+ source = _plugin(' public Object run() {\n'
238
+ ' return QueryServiceHelper.query(entity(), fields(), filter(), null, limit());\n }')
239
+ assert "CVE-KD-0303" not in _codes(dcs_rule_issues(source))
240
+ assert "CVE-KD-0303" in _codes(dcs_rule_warnings(source))
241
+ source = _plugin(' public boolean run(String entity) {\n'
242
+ ' return QueryServiceHelper.exists(entity, null);\n }')
243
+ assert "CVE-KD-0303" in _codes(dcs_rule_issues(source))
244
+
245
+
246
+ @pytest.mark.parametrize("cipher,blocked", [
247
+ ("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", False),
248
+ ("RSA/ECB/PKCS1Padding", True),
249
+ ("RSA", True),
250
+ ("AES/GCM/NoPadding", False),
251
+ ("AES/ECB/PKCS5Padding", True),
252
+ ])
253
+ def test_rsa_oaep_is_not_a_symmetric_ecb_cipher(cipher, blocked):
254
+ source = _plugin(f' public Object run() {{\n return Cipher.getInstance("{cipher}");\n }}')
255
+ assert ("java:S5542" in _codes(dcs_rule_issues(source))) is blocked
256
+
257
+
258
+ @pytest.mark.parametrize("kind,algorithm,bits,blocked", [
259
+ ("KeyPairGenerator", "EC", 256, False),
260
+ ("KeyPairGenerator", "EC", 224, False),
261
+ ("KeyPairGenerator", "EC", 192, True),
262
+ ("KeyPairGenerator", "RSA", 2048, False),
263
+ ("KeyPairGenerator", "RSA", 1024, True),
264
+ ("KeyGenerator", "AES", 128, False),
265
+ ("KeyGenerator", "AES", 64, True),
266
+ ])
267
+ def test_key_lengths_are_checked_on_the_correct_generator(kind, algorithm, bits, blocked):
268
+ method = "initialize" if kind == "KeyPairGenerator" else "init"
269
+ source = _plugin(f' public void run(Other unrelated) {{\n'
270
+ f' {kind} generator = {kind}.getInstance("{algorithm}");\n'
271
+ f' generator.{method}({bits});\n unrelated.{method}(1);\n }}')
272
+ issues = [item for item in scan_source(source) if "java:S4426" in item["ruleCodes"]]
273
+ assert len(issues) == int(blocked)
274
+ if blocked:
275
+ assert algorithm in issues[0]["fix"]
276
+ assert f"generator.{method}({bits})" in source.splitlines()[issues[0]["line"] - 1]
277
+
278
+
279
+ def test_a_reassigned_generator_does_not_inherit_the_previous_algorithm():
280
+ source = _plugin(' public void run() {\n'
281
+ ' KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");\n'
282
+ ' generator.initialize(2048);\n'
283
+ ' generator = KeyPairGenerator.getInstance("EC");\n'
284
+ ' generator.initialize(192);\n }')
285
+ issues = [item for item in scan_source(source) if "java:S4426" in item["ruleCodes"]]
286
+ assert len(issues) == 1 and "EC" in issues[0]["fix"]
287
+
288
+
289
+ def test_same_generator_names_in_other_methods_do_not_inherit_the_algorithm():
290
+ source = _plugin(' public void first() {\n'
291
+ ' KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");\n'
292
+ ' generator.initialize(2048);\n }\n'
293
+ ' public void second() {\n'
294
+ ' KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");\n'
295
+ ' generator.initialize(256);\n }')
296
+ assert "java:S4426" not in _codes(scan_source(source))
297
+
298
+
299
+ @pytest.mark.parametrize("setting,blocked", [
300
+ ('factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);', False),
301
+ ('factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);', True),
302
+ ('factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);', True),
303
+ ('factory.setExpandEntityReferences(false);', True),
304
+ ('String note = "disallow-doctype-decl";', True),
305
+ ('factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);\n'
306
+ ' factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);', True),
307
+ ])
308
+ def test_xxe_hardening_requires_effective_settings_on_this_factory(setting, blocked):
309
+ source = _plugin(' public Object run() {\n'
310
+ ' DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n'
311
+ f' {setting}\n return factory.newDocumentBuilder();\n }}')
312
+ assert ("java:S2755" in _codes(dcs_rule_issues(source))) is blocked
313
+
314
+
315
+ def test_the_same_factory_name_in_another_method_is_not_implicitly_hardened():
316
+ source = _plugin(' public Object first() {\n'
317
+ ' DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n'
318
+ ' factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);\n'
319
+ ' return factory.newDocumentBuilder();\n }\n'
320
+ ' public Object second() {\n'
321
+ ' DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n'
322
+ ' return factory.newDocumentBuilder();\n }')
323
+ issues = [item for item in scan_source(source) if "java:S2755" in item["ruleCodes"]]
324
+ assert len(issues) == 1 and issues[0]["line"] > source.splitlines().index(' public Object second() {') + 1
325
+
326
+
327
+ def test_one_hardened_factory_does_not_clear_another_or_an_earlier_parse():
328
+ source = _plugin(' public Object run() {\n'
329
+ ' DocumentBuilderFactory safe = DocumentBuilderFactory.newInstance();\n'
330
+ ' safe.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);\n'
331
+ ' DocumentBuilderFactory unsafe = DocumentBuilderFactory.newInstance();\n'
332
+ ' return unsafe.newDocumentBuilder();\n }')
333
+ findings = [item for item in scan_source(source) if "java:S2755" in item["ruleCodes"]]
334
+ assert len(findings) == 1
335
+ assert "DocumentBuilderFactory unsafe" in source.splitlines()[findings[0]["line"] - 1]
336
+ late = _plugin(' public void run() {\n'
337
+ ' DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n'
338
+ ' factory.newDocumentBuilder();\n'
339
+ ' factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);\n }')
340
+ assert "java:S2755" in _codes(dcs_rule_issues(late))
341
+
342
+
343
+ @pytest.mark.parametrize("kind,settings", [
344
+ ("XMLInputFactory", 'factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);\n'
345
+ ' factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);'),
346
+ ("TransformerFactory", 'factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");\n'
347
+ ' factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");'),
348
+ ("SchemaFactory", 'factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");\n'
349
+ ' factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");'),
350
+ ])
351
+ def test_factory_specific_xxe_controls_are_not_interchangeable(kind, settings):
352
+ source = _plugin(f' public Object run() {{\n {kind} factory = {kind}.newInstance();\n'
353
+ f' {settings}\n return factory;\n }}')
354
+ assert "java:S2755" not in _codes(scan_source(source))
355
+ assert "java:S2755" in _codes(dcs_rule_issues(source.replace(settings.splitlines()[0], "")))
356
+
357
+
358
+ def test_runtime_or_conditional_xxe_settings_are_not_claimed_as_proven_safe():
359
+ for setting in (
360
+ 'factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", enabled);',
361
+ 'if (enabled) { factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); }',
362
+ 'if (enabled) factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);',
363
+ ):
364
+ source = _plugin(' public Object run(boolean enabled) {\n'
365
+ ' DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n'
366
+ f' {setting}\n return factory.newDocumentBuilder();\n }}')
367
+ assert "java:S2755" not in _codes(dcs_rule_issues(source))
368
+ assert "java:S2755" in _codes(dcs_rule_warnings(source))
369
+
370
+
371
+ @pytest.mark.parametrize("expression,blocked", [
372
+ ('new BigInteger("42")', True),
373
+ ('new BigInteger("9223372036854775808")', False),
374
+ ('new BigInteger(bytes)', False),
375
+ ('new BigInteger(1, bytes)', False),
376
+ ('new BigInteger(text)', False),
377
+ ('new BigInteger(text, 16)', False),
378
+ ('new java.math.BigInteger(bytes)', False),
379
+ ])
380
+ def test_big_integer_constructors_are_only_replaced_when_value_of_is_equivalent(expression, blocked):
381
+ source = _plugin(' public Object run(byte[] bytes, String text) {\n'
382
+ f' return {expression};\n }}')
383
+ assert ("java:S2129" in _codes(dcs_rule_issues(source))) is blocked
384
+
385
+
386
+ def test_every_try_resource_is_implicitly_used_but_ordinary_unused_locals_are_not():
387
+ source = _plugin(' public void run() {\n'
388
+ ' try (Scope first = open(); Scope second = open(); Scope third = open()) {\n'
389
+ ' Scope unused = open();\n work();\n }\n }')
390
+ issues = [item for item in scan_source(source) if "java:S1481" in item["ruleCodes"]]
391
+ assert len(issues) == 1 and "unused" in issues[0]["message"]
392
+
393
+
394
+ @pytest.mark.parametrize("body", [
395
+ 'public Object run(Runnable work) { return new Thread(work); }',
396
+ 'public Object run(Runnable work) { return new java.lang.Thread(work); }',
397
+ 'public class Worker extends Thread { public void run() { work(); } }',
398
+ 'public class Worker extends java.lang.Thread { public void run() { work(); } }',
399
+ ])
400
+ def test_native_threads_cannot_bypass_the_gate_with_fully_qualified_names(body):
401
+ assert "CVE-KD-0055-1" in _codes(dcs_rule_issues(_plugin(body)))
402
+
403
+
404
+ def test_update_view_in_property_changed_is_allowed_once_guarded_on_the_field():
405
+ unguarded = _plugin(" public void propertyChanged(PropertyChangedArgs e) {\n getView().updateView();\n }")
406
+ guarded = _plugin(
407
+ " public void propertyChanged(PropertyChangedArgs e) {\n"
408
+ " if (!\"qty\".equals(e.getProperty().getName())) {\n return;\n }\n"
409
+ " getView().updateView(\"qty\");\n }")
410
+
411
+ assert "CVE-KD-2026-0618" in _codes(scan_source(unguarded))
412
+ assert "CVE-KD-2026-0618" not in _codes(scan_source(guarded))
413
+
414
+
415
+ def test_a_rethrown_throwable_is_advisory_and_a_swallowed_one_is_blocked():
416
+ rethrown = _plugin(
417
+ " public void run(Runnable work) {\n try {\n work.run();\n"
418
+ " } catch (Throwable e) {\n LOG.error(\"failed\", e);\n throw e;\n }\n }")
419
+ swallowed = rethrown.replace("throw e;", "LOG.warn(\"ignored\");")
420
+
421
+ assert "java:S1181" not in _codes(dcs_rule_issues(rethrown))
422
+ assert "java:S1181" in _codes(dcs_rule_warnings(rethrown))
423
+ assert "java:S1181" in _codes(dcs_rule_issues(swallowed))
424
+
425
+
426
+ def test_hardcoded_credentials_are_blocked_but_sql_text_naming_a_column_is_not():
427
+ hardcoded = _plugin(' private static final String PASSWORD = "Kd@123456";')
428
+ sql_text = _plugin(' public String sql(String password) {\n'
429
+ ' return "where fpassword=\'" + password + "\'";\n }')
430
+
431
+ assert "java:S2068" in _codes(dcs_rule_issues(hardcoded))
432
+ assert "java:S2068" not in _codes(scan_source(sql_text))
433
+
434
+
435
+ def test_shapes_that_only_look_like_a_violation_are_not_reported():
436
+ # A type annotation inside the return type is not the method name.
437
+ annotated = _plugin(' public Result<@Body("x") Boolean> saveMap(String key) {\n return Result.of(key);\n }')
438
+ assert "java:S100" not in _codes(scan_source(annotated))
439
+ # An operator inside a string literal is text, not a comparison.
440
+ literal = _plugin(' public String text(String a, String b) {\n return a + " != " + b;\n }')
441
+ assert "java:S4973" not in _codes(scan_source(literal))
442
+ # toArray with a sized array is the accepted form; a zero-length one is not.
443
+ sized = _plugin(" public String[] run(java.util.List<String> keys) {\n"
444
+ " return keys.toArray(new String[keys.size()]);\n }")
445
+ assert "ITA_INEFFICIENT_TO_ARRAY" not in _codes(scan_source(sized))
446
+ assert "ITA_INEFFICIENT_TO_ARRAY" in _codes(scan_source(sized.replace("keys.size()", "0")))
447
+
448
+
449
+ def test_comments_and_literals_never_trigger_a_forbidden_type():
450
+ source = _plugin(' // do not use kd.bos.orm.ORM here\n'
451
+ ' public String note() {\n return "kd.bos.db.DB is forbidden";\n }')
452
+
453
+ assert not {"CVE-KD-2026-0508", "CVE-KD-0413"} & _codes(scan_source(source))
454
+
455
+
456
+ def test_warnings_carry_the_compile_gate_shape():
457
+ source = _plugin(" public int run() {\n // int total = compute(1, 2);\n return 1;\n }")
458
+ warnings = dcs_rule_warnings(source)
459
+
460
+ assert [item["ruleCodes"] for item in warnings] == [["java:S125"]]
461
+ assert warnings[0]["severity"] == "warning"
462
+ assert warnings[0]["errorCode"] == "PLUGIN_SOURCE_DCS_RULE_ADVISORY"
463
+ assert dcs_rule_issues(source) == []
464
+
465
+
466
+ def test_sql_scripts_are_limited_to_the_schema_changes_dcs_allows():
467
+ issues = scan_sql(
468
+ b"-- delete from t_old\n"
469
+ b"create table t_demo_a_very_long_table_name_x (fid bigint);\n"
470
+ b"insert into t_demo values (1);\n"
471
+ b"create or replace view v_demo as select 1;\n")
472
+
473
+ assert _codes(issues) == {"CVE-KD-SQL-0014", "CVE-KD-SQL-0005", "CVE-KD-SQL-0001", "CVE-KD-SQL-0017"}
474
+ assert [issue["line"] for issue in issues if issue["ruleCodes"] == ["CVE-KD-SQL-0005"]] == [3]
475
+ assert _codes(scan_sql("create table t_x (fid bigint);".encode("gbk") + "-- 中文".encode("gbk"))) == {
476
+ "CVE-KD-SQL-0015"}
477
+
478
+
479
+ @pytest.mark.parametrize("literal", [
480
+ "'insert into t_demo'", "'delete from t_demo'", "'drop table t_demo'",
481
+ "'it''s -- text'", "'/* text */'", "'first\nupdate t_demo set fid=1'",
482
+ "$$delete from t_demo$$", "$tag$insert into t_demo$tag$",
483
+ ])
484
+ def test_sql_literal_keywords_and_comment_delimiters_do_not_hide_real_statements(literal):
485
+ sql = f"create table t_demo (note varchar(100) default {literal});\nupdate t_demo set note='ok';"
486
+ issues = scan_sql(sql.encode("utf-8"))
487
+ assert _codes(issues) == {"CVE-KD-SQL-0006"}
488
+ assert issues[0]["line"] == sql.count("\n") + 1
489
+
490
+
491
+ @pytest.mark.parametrize("quoted", ['"t_demo"', '`t_demo`', '[t_demo]', '"schema"."t_demo"'])
492
+ def test_sql_quoted_identifiers_preserve_real_updates_and_schema_rules(quoted):
493
+ assert _codes(scan_sql(f"update {quoted} set fid=1;".encode())) == {"CVE-KD-SQL-0006"}
494
+ assert _codes(scan_sql(f"alter table {quoted} drop column fid;".encode())) == {"CVE-KD-SQL-0008"}
495
+ assert scan_sql(f"create table {quoted} (fid bigint);".encode()) == []
496
+ assert _codes(scan_sql(('create table "schema"."' + "t" * 25 + '" (fid bigint);').encode())) == {"CVE-KD-SQL-0014"}
497
+
498
+
499
+ def test_quoted_sql_keywords_and_comments_are_not_executable_code():
500
+ assert scan_sql(b'create table t_demo ("insert into" varchar(100));\n'
501
+ b'-- update t_demo set fid=1\n/* delete from t_demo */') == []
502
+
503
+
504
+ def _cli(*args):
505
+ run = subprocess.run([sys.executable, str(SCRIPTS / "dcs_code_gate.py"), *args],
506
+ capture_output=True, text=True)
507
+ return run.returncode, json.loads(run.stdout)
508
+
509
+
510
+ def test_the_cli_scans_a_module_and_fails_on_blocking_findings(tmp_path):
511
+ package = tmp_path / "src" / "main" / "java" / "cus" / "demo" / "formplugin"
512
+ package.mkdir(parents=True)
513
+ (package / "MaterialPricePlugin.java").write_text(_GOOD, encoding="utf-8")
514
+ (tmp_path / "build").mkdir()
515
+ (tmp_path / "build" / "Bad.java").write_text(_BAD, encoding="utf-8")
516
+
517
+ code, report = _cli("--source", str(tmp_path))
518
+ assert code == 0
519
+ assert report["status"] is True and report["scannedFiles"] == 1
520
+ assert report["ruleSource"] == "金蝶AI套件公有云门禁20261026.xlsx ∪ 扫描规则_1026144356.xlsx(高危/严重)"
521
+ assert report["ruleSources"] == dcs_code_gate.CATALOG["sources"]
522
+ assert report["catalogueMerge"] == {"overlapCount": 313, "addedCount": 115}
523
+ assert report["coverage"] == {"cataloguedRuleCount": 1582, "scope": "source-text", "complete": False}
524
+ assert report["missing"] == report["scanErrors"] == report["emptySources"] == []
525
+
526
+ (tmp_path / "lib").mkdir()
527
+ (tmp_path / "lib" / "third.jar").write_bytes(b"PK")
528
+ (package / "Bad.java").write_text(_BAD, encoding="utf-8")
529
+ code, report = _cli("--source", str(tmp_path))
530
+ assert code == 2
531
+ assert report["status"] is False
532
+ assert {"CVE-KD-SCAN-jar", "CVE-KD-2026-0508"} <= _codes(report["findings"])
533
+ assert all(Path(item["file"]).name in {"Bad.java", "third.jar"} for item in report["findings"])
534
+
535
+
536
+ def test_the_cli_fails_on_advisories_only_in_strict_mode_and_on_a_missing_path(tmp_path):
537
+ source = tmp_path / "cus" / "demo" / "Demo.java"
538
+ source.parent.mkdir(parents=True)
539
+ source.write_text(_plugin(" public int run() {\n // int total = compute(1, 2);\n return 1;\n }"),
540
+ encoding="utf-8")
541
+
542
+ code, report = _cli("--source", str(source))
543
+ assert code == 0 and report["advisoryCount"] == 1
544
+ code, report = _cli("--source", str(source), "--strict")
545
+ assert code == 2 and report["status"] is False
546
+ code, report = _cli("--source", str(tmp_path / "absent"))
547
+ assert code == 2 and report["missing"]
548
+
549
+
550
+ @pytest.mark.parametrize("suffix", [".jar", ".class", ".so", ".dll", ".dylib", ".JAR"])
551
+ def test_the_cli_rejects_a_binary_passed_directly(tmp_path, suffix):
552
+ source = tmp_path / f"dependency{suffix}"
553
+ source.write_bytes(b"binary")
554
+
555
+ code, report = _cli("--source", str(source))
556
+
557
+ assert code == 2 and report["status"] is False
558
+ assert report["emptySources"] == []
559
+ assert report["findingCount"] == 1
560
+ assert _codes(report["findings"]) == {
561
+ "CVE-KD-SCAN-jar" if suffix.lower() == ".jar" else "CVE-KD-SCAN-libFile"}
562
+ assert report["findings"][0]["file"] == str(source.resolve())
563
+
564
+
565
+ @pytest.mark.parametrize("kind", ["empty", "unsupported", "build-only"])
566
+ def test_the_cli_never_reports_unscanned_input_as_a_clean_module(tmp_path, kind):
567
+ source = tmp_path
568
+ if kind == "unsupported":
569
+ source = tmp_path / "notes.txt"
570
+ source.write_text("notes", encoding="utf-8")
571
+ if kind == "build-only":
572
+ (tmp_path / "build").mkdir()
573
+ (tmp_path / "build" / "Bad.java").write_text(_BAD, encoding="utf-8")
574
+
575
+ code, report = _cli("--source", str(source))
576
+
577
+ assert code == 2 and report["status"] is False
578
+ assert report["scannedFiles"] == report["findingCount"] == 0
579
+ assert report["emptySources"] == [str(source.resolve())]
580
+
581
+
582
+ def test_the_cli_reports_read_errors_and_keeps_scanning_other_inputs(tmp_path, monkeypatch, capsys):
583
+ unreadable = tmp_path / "broken.sql"
584
+ readable = tmp_path / "valid.sql"
585
+ unreadable.write_bytes(b"create table t_broken (fid bigint);")
586
+ readable.write_bytes(b"create table t_demo (fid bigint);")
587
+ read_bytes = Path.read_bytes
588
+
589
+ def read(path):
590
+ if path == unreadable:
591
+ raise PermissionError("test read denied")
592
+ return read_bytes(path)
593
+
594
+ monkeypatch.setattr(Path, "read_bytes", read)
595
+ monkeypatch.setattr(sys, "argv", ["dcs_code_gate.py", "--source", str(unreadable), "--source", str(readable)])
596
+
597
+ assert dcs_code_gate.main() == 2
598
+ report = json.loads(capsys.readouterr().out)
599
+ assert report["status"] is False
600
+ assert report["scanErrors"] == [{"file": str(unreadable), "message": "test read denied"}]
601
+ assert report["scannedFiles"] == 1 and report["findingCount"] == 0
602
+
603
+
604
+ @pytest.mark.parametrize("relative", [
605
+ "examples/java-rules/CosmicReportFilters.java",
606
+ "examples/java-rules/CosmicReportAggregation.java",
607
+ ])
608
+ def test_report_helpers_remain_free_of_dcs_source_violations(relative):
609
+ path = SCRIPTS.parent / relative
610
+ issues = dcs_rule_issues(path.read_text(encoding="utf-8"))
611
+ # 独立 SDK 测试的默认包 helper 不是可直接交付的插件;只在这两个夹具中允许默认包。
612
+ assert not [issue for issue in issues if issue["ruleCodes"] != ["java:S1220"]]
613
+
614
+
615
+ def test_catalogue_is_the_exact_union_and_preserves_original_lower_risks():
616
+ catalog = dcs_code_gate.CATALOG
617
+ original = {code for code, entry in catalog["rules"].items() if 0 in entry["sources"]}
618
+ selected = {code for code, entry in catalog["rules"].items() if 1 in entry["sources"]}
619
+ assert len(original) == 1467 and len(selected) == 428
620
+ assert len(original & selected) == 313 and len(selected - original) == 115
621
+ assert original | selected == catalog["rules"].keys()
622
+ for index, codes in enumerate((original, selected)):
623
+ assert hashlib.sha256("\n".join(sorted(codes)).encode()).hexdigest() == catalog["sources"][index]["codeSetSha256"]
624
+ assert all(entry["sourceRisks"][entry["sources"].index(1)] in {"高危", "严重"} for entry in catalog["rules"].values() if 1 in entry["sources"])
625
+ assert any(entry["risk"] == "低危" for entry in catalog["rules"].values())
626
+ assert any(entry["risk"] == "中危" for entry in catalog["rules"].values())
627
+ assert len(catalog["rules"]) == 1582
628
+ assert set(RULES) - catalog["rules"].keys() == {alias for alias in dcs_code_gate._FORTIFY_NAMES if alias.startswith("FORTIFY:")}
629
+ assert all(dcs_code_gate._FORTIFY_CODES.values())
630
+
631
+
632
+ def test_shared_rule_codes_keep_the_stronger_risk_without_duplicate_registration():
633
+ for code, entry in dcs_code_gate.CATALOG["rules"].items():
634
+ assert RULES[code][0] == min(entry["sourceRisks"], key=dcs_code_gate._RISK_ORDER.__getitem__)
635
+ assert RULES["CVE-KD-2026-0508"][0] == "严重"
636
+ assert RULES["CVE-KD-0056-02"][0] == "高危"
637
+ assert RULES["CVE-KD-SQL-0015"][0] == "严重"
638
+ assert scan_sql(b"\xff")[0]["dcsRisk"] == "严重"
639
+
640
+
641
+ _LOOP_CATALOG = [(code, entry["check"]["target"]) for code, entry in dcs_code_gate.CATALOG["rules"].items()
642
+ if code.startswith("CVE-KD-0051-") and entry.get("check", {}).get("kind") == "循环里面指定方法"]
643
+
644
+
645
+ @pytest.mark.parametrize("code,target", _LOOP_CATALOG)
646
+ @pytest.mark.parametrize("receiver", ["simple", "qualified", "instance"])
647
+ def test_every_added_loop_method_is_checked_with_its_real_rule_code(code, target, receiver):
648
+ owner, method = target.rsplit(".", 1)
649
+ simple = owner.rsplit(".", 1)[-1]
650
+ name = owner if receiver == "qualified" else "helper" if receiver == "instance" else simple
651
+ params = f"{simple} helper, " if receiver == "instance" else ""
652
+ source = _plugin(f" public void run({params}List<Object> rows) {{\n"
653
+ f" for (Object row : rows) {{ {name}.{method}(row); }}\n }}", f"import {owner};\nimport java.util.List;")
654
+ assert code in _codes(dcs_rule_issues(source))
655
+ outside = source.replace(f"for (Object row : rows) {{ {name}.{method}(row); }}", f"{name}.{method}(rows);")
656
+ assert code not in _codes(scan_source(outside))
657
+
658
+
659
+ @pytest.mark.parametrize("code,call", [
660
+ ("CVE-KD-0059-2", "MetadataDao.getFormDeployFile(key)"),
661
+ ("CVE-KD-0059-3", "kd.bos.metadata.dao.MetadataDao.refreshMainEntityLName(key)"),
662
+ ])
663
+ def test_additional_metadata_methods_are_rejected(code, call):
664
+ source = _plugin(f"public Object run(String key) {{ return {call}; }}")
665
+ assert code in _codes(dcs_rule_issues(source))
666
+
667
+
668
+ @pytest.mark.parametrize("body,code", [
669
+ ('public Object run() { SecureRandom random = new SecureRandom(); random.setSeed(123L); return random; }', "java:S4347"),
670
+ ('public Boolean run() { return null; }', "java:S2447"),
671
+ ('protected void finalize() { work(); }', "java:S1113"),
672
+ ('public void run() { try { work(); } finally { throw new IllegalStateException(); } }', "java:S1163"),
673
+ ('public int run() { try { work(); } finally { return 1; } }', "java:S1143"),
674
+ ('public void run(Thread worker) { worker.notifyAll(); }', "java:S2236"),
675
+ ('public void run(java.sql.PreparedStatement stmt) { stmt.setString(0, "x"); }', "java:S2695"),
676
+ ('public Object run() { return new java.lang.ThreadGroup("worker"); }', "java:S3014"),
677
+ ('public void run() { System.runFinalizersOnExit(true); }', "java:S2151"),
678
+ ('public void run(HttpsURLConnection connection) { connection.setHostnameVerifier((host, session) -> true); }', "java:S5527"),
679
+ ('public String run() { return "/*dialect*/ select id from t_demo"; }', "CVE-KD-0060-1"),
680
+ ('public String run() { return "CREATE VIEW v_demo AS SELECT 1"; }', "CVE-KD-0720-1"),
681
+ ])
682
+ def test_new_high_risk_source_patterns_block(body, code):
683
+ assert code in _codes(dcs_rule_issues(_plugin(body)))
684
+
685
+
686
+ def test_certificate_and_hostname_verification_cannot_be_replaced_by_noops():
687
+ for interface, body, code in (
688
+ ("X509TrustManager", 'public void checkServerTrusted(X509Certificate[] chain, String authType) { /* deliberately empty */ }', "java:S4830"),
689
+ ("HostnameVerifier", 'public boolean verify(String host, SSLSession session) { return true; }', "java:S5527"),
690
+ ):
691
+ source = f"package cus.demo; public class Demo implements {interface} {{ {body} }}"
692
+ findings = [issue for issue in dcs_rule_issues(source) if code in issue["ruleCodes"]]
693
+ assert len(findings) == 1
694
+ assert findings[0]["relatedRuleCodes"]
695
+ safe = source.replace("/* deliberately empty */", "validate(chain, authType);").replace("return true;", "return defaultVerifier.verify(host, session);")
696
+ assert code not in _codes(scan_source(safe))
697
+
698
+
699
+ def test_safe_seed_jdbc_and_monitor_operations_are_not_flagged():
700
+ source = _plugin('public Object run(byte[] seed, Object lock, ResultSet result) {\n'
701
+ ' SecureRandom random = new SecureRandom(seed); random.setSeed(seed);\n'
702
+ ' lock.notifyAll(); result.getString(1); return random;\n}')
703
+ assert not {"java:S4347", "java:S2236", "java:S2695"} & _codes(scan_source(source))
704
+
705
+
706
+ @pytest.mark.parametrize("expression,name", [
707
+ ('Runtime.getRuntime().exec(command)', "Command Injection"),
708
+ ('new ProcessBuilder(command)', "Command Injection"),
709
+ ('engine.eval(command)', "Dynamic Code Evaluation: Script Injection"),
710
+ ('new java.net.URL(address)', "Server-Side Request Forgery"),
711
+ ('response.sendRedirect(address)', "Open Redirect"),
712
+ ('response.setHeader("Location", address)', "Header Manipulation"),
713
+ ('new java.io.File(address)', "Path Manipulation"),
714
+ ])
715
+ def test_potential_injections_require_review_not_a_false_proven_block(expression, name):
716
+ source = _plugin(f"public Object run(String command, String address) {{ return {expression}; }}")
717
+ codes = {code for code, entry in dcs_code_gate.CATALOG["rules"].items() if entry["engine"] == "fortify" and entry["name"] == name}
718
+ assert codes & _codes(dcs_rule_warnings(source))
719
+ assert not codes & _codes(dcs_rule_issues(source))
720
+
721
+
722
+ def test_resources_are_advisory_when_ownership_is_unknown_but_try_resources_are_valid():
723
+ source = _plugin('public int run(String file) { FileInputStream stream = new FileInputStream(file); return stream.read(); }')
724
+ assert {"java:S2093", "java:S2095"} <= _codes(dcs_rule_warnings(source))
725
+ assert "java:S2095" not in _codes(dcs_rule_issues(source))
726
+ safe = _plugin('public int run(String file) { try (FileInputStream first = new FileInputStream(file); FileInputStream second = new FileInputStream(file)) { return first.read() + second.read(); } }')
727
+ assert not {"java:S2093", "java:S2095", "java:S1481"} & _codes(scan_source(safe))
728
+
729
+
730
+ def test_fortify_compatibility_aliases_link_to_real_spreadsheet_ids():
731
+ source = _plugin('private static final String PASSWORD = "secret";')
732
+ issue = next(issue for issue in scan_source(source) if "java:S2068" in issue["ruleCodes"])
733
+ assert issue["relatedRuleCodes"]
734
+ assert all(dcs_code_gate.CATALOG["rules"][code]["name"] == "Password Management: Hardcoded Password" for code in issue["relatedRuleCodes"])
735
+ assert "5FC849F9-EBBB-49C9-8F48-45368F98C83E-1" in issue["relatedRuleCodes"]
736
+
737
+
738
+ @pytest.mark.parametrize("sql,code", [
739
+ ('INSERT INTO "t_demo" VALUES (1);', "CVE-KD-SQL-0017"),
740
+ ("DELETE t_demo WHERE fid=1;", "CVE-KD-SQL-0007"),
741
+ ("EXEC p_AlterColumn 't_demo', 'fid', 'NULL';", "CVE-KD-SQL-0008"),
742
+ ("EXEC p_modifyobjectname 't_demo', 't_new';", "CVE-KD-SQL-0010"),
743
+ ])
744
+ def test_new_sql_rules_detect_statements_and_ignore_strings_comments(sql, code):
745
+ assert code in _codes(scan_sql(sql.encode()))
746
+ assert code not in _codes(scan_sql(("-- " + sql).encode()))
747
+ assert code not in _codes(scan_sql(("SELECT '" + sql.replace("'", "''") + "';").encode()))
748
+
749
+
750
+ def test_sql_dialect_comments_preserve_offsets_and_are_contextual_advice():
751
+ sql = "/*dialect*/\nUPDATE t_demo SET fid=1;"
752
+ issues = scan_sql(sql.encode())
753
+ assert next(issue for issue in issues if issue["ruleCodes"] == ["CVE-KD-SQL-0006"])["line"] == 2
754
+ assert next(issue for issue in issues if issue["ruleCodes"] == ["CVE-KD-SQL-0016"])["level"] == "advise"
755
+ for text in ("SELECT '/*dialect*/';", 'CREATE TABLE "/*dialect*/" (fid int);', "/* documentation /*dialect*/ */"):
756
+ assert "CVE-KD-SQL-0016" not in _codes(scan_sql(text.encode()))
757
+ assert "CVE-KD-SQL-0017" not in _codes(scan_sql(b"INSERT INTO t_demo(fid) VALUES (1);"))
758
+
759
+
760
+ def test_big_table_warnings_are_not_proven_violations_and_strict_cli_can_enforce_them(tmp_path):
761
+ sql = tmp_path / "changes.sql"
762
+ sql.write_text("CREATE INDEX idx ON t_demo(fid);\nALTER TABLE t_demo ADD COLUMN note varchar(100);\nALTER TABLE t_demo ALTER COLUMN note TYPE varchar(200);", encoding="utf-8")
763
+ code, report = _cli("--source", str(sql))
764
+ assert code == 0 and not report["findings"]
765
+ assert _codes(report["advisories"]) == {"CVE-KD-SQL-0011", "CVE-KD-SQL-0012", "CVE-KD-SQL-0013"}
766
+ assert _cli("--source", str(sql), "--strict")[0] == 2
767
+
768
+
769
+ def test_duplicate_top_level_class_names_are_blocked_within_a_scanned_module(tmp_path):
770
+ for directory in ("src", "test"):
771
+ java = tmp_path / directory / "cus" / "demo" / "Demo.java"
772
+ java.parent.mkdir(parents=True)
773
+ java.write_text(_plugin('public String run() { return "ok"; }'), encoding="utf-8")
774
+ code, report = _cli("--source", str(tmp_path))
775
+ assert code == 2
776
+ conflicts = [issue for issue in report["findings"] if issue["ruleCodes"] == ["class_fully_qualified_name_conflict_internal"]]
777
+ assert len(conflicts) == 2
778
+
779
+
780
+ @pytest.mark.parametrize("body", [
781
+ 'public void run() { try { work(); } finally { Runnable job = () -> { return; }; use(job); } }',
782
+ 'public void run() { try { work(); } finally { class Local { int value() { return 1; } } use(new Local()); } }',
783
+ 'public void run() { try { work(); } finally { Runnable job = new Runnable() { public void run() { return; } }; use(job); } }',
784
+ 'public void run() { try { work(); } finally { for (int i = 0; i < 2; i++) { if (i == 1) break; continue; } } }',
785
+ 'public void run(int n) { try { work(); } finally { switch (n) { case 1: work(); break; default: work(); } } }',
786
+ ])
787
+ def test_finally_does_not_confuse_nested_function_or_local_loop_jumps_with_escaping(body):
788
+ assert not {"java:S1143", "java:S1163", "FORTIFY:Return Inside Finally"} & _codes(scan_source(_plugin(body)))
789
+
790
+
791
+ def test_a_caught_throw_in_finally_needs_review_instead_of_a_proven_block():
792
+ source = _plugin('public void run() { try { work(); } finally { try { throw new IllegalStateException(); } catch (IllegalStateException e) { recover(e); } } }')
793
+ assert "java:S1163" not in _codes(dcs_rule_issues(source))
794
+ assert "java:S1163" in _codes(dcs_rule_warnings(source))
795
+
796
+
797
+ @pytest.mark.parametrize("body", [
798
+ 'public void run(PreparedStatement stmt) { stmt.setFetchSize(0); stmt.setMaxRows(0); stmt.setQueryTimeout(0); }',
799
+ 'public void run(ResultSet result) { result.setFetchSize(0); result.getString("id"); result.getString(1); }',
800
+ ])
801
+ def test_jdbc_limits_and_named_columns_are_not_mistaken_for_invalid_indices(body):
802
+ assert "java:S2695" not in _codes(scan_source(_plugin(body)))
803
+
804
+
805
+ @pytest.mark.parametrize("body", [
806
+ 'public void run(ResultSet result) { result.getObject(0); }',
807
+ 'public void run(CallableStatement stmt) { stmt.registerOutParameter(0, 12); }',
808
+ ])
809
+ def test_jdbc_index_zero_is_invalid_for_columns_and_output_parameters(body):
810
+ assert "java:S2695" in _codes(dcs_rule_issues(_plugin(body)))
811
+
812
+
813
+ def test_non_constant_seed_array_elements_are_not_proven_predictable():
814
+ source = _plugin('public Object run() { return new SecureRandom(new byte[]{entropy()}); }')
815
+ assert "java:S4347" not in _codes(scan_source(source))
816
+ for seed in ("new byte[16]", "new byte[]{1, 2, 3}"):
817
+ assert "java:S4347" in _codes(dcs_rule_issues(_plugin(f'public Object run() {{ return new SecureRandom({seed}); }}')))
818
+
819
+
820
+ @pytest.mark.parametrize("body", [
821
+ 'public void run(HttpsURLConnection connection) { connection.setHostnameVerifier((host, session) -> { return true; }); }',
822
+ 'public void run() { javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier((host, session) -> true); }',
823
+ ])
824
+ def test_both_https_hostname_verifier_setters_reject_always_true_lambdas(body):
825
+ assert "java:S5527" in _codes(dcs_rule_issues(_plugin(body)))
826
+
827
+
828
+ @pytest.mark.parametrize("body,blocked", [
829
+ ('public Boolean run() { class Local { Object value() { return null; } } use(new Local()); return Boolean.TRUE; }', False),
830
+ ('public Boolean run() { Supplier<Object> value = () -> { return null; }; use(value); return Boolean.TRUE; }', False),
831
+ ('public Boolean run() { Supplier<Object> value = new Supplier<Object>() { public Object get() { return null; } }; use(value); return Boolean.TRUE; }', False),
832
+ ('public Boolean run() { Supplier<Object> value = () -> { return null; }; use(value); return null; }', True),
833
+ ('public Boolean run() { class Local { Object value() { return null; } } use(new Local()); return null; }', True),
834
+ ])
835
+ def test_boolean_null_returns_belong_to_the_declaring_function(body, blocked):
836
+ assert ("java:S2447" in _codes(dcs_rule_issues(_plugin(body)))) is blocked
837
+
838
+
839
+ @pytest.mark.parametrize("shape", ["anonymous", "local"])
840
+ @pytest.mark.parametrize("interface,body,code", [
841
+ ("javax.net.ssl.X509TrustManager", 'public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { /* deliberately empty */ } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { validate(chain, authType); } public X509Certificate[] getAcceptedIssuers() { return issuers(); }', "java:S4830"),
842
+ ("javax.net.ssl.HostnameVerifier", 'public boolean verify(String host, SSLSession session) { return true; }', "java:S5527"),
843
+ ])
844
+ def test_tls_verification_in_method_local_implementations_is_checked(shape, interface, body, code):
845
+ declaration = f"return new {interface}() {{ {body} }};" if shape == "anonymous" else f"class Local implements {interface} {{ {body} }} return new Local();"
846
+ source = _plugin(f"public Object run() {{ {declaration} }}")
847
+ findings = [issue for issue in dcs_rule_issues(source) if code in issue["ruleCodes"]]
848
+ assert len(findings) == 1
849
+ assert findings[0]["relatedRuleCodes"]
850
+ safe = source.replace("/* deliberately empty */", "validate(chain, authType);").replace("return true;", "return defaultVerifier.verify(host, session);")
851
+ assert code not in _codes(scan_source(safe))
852
+
853
+
854
+ @pytest.mark.parametrize("body,blocked", [
855
+ ('public java.util.List<Object> run() { class Local { Object value() { return null; } } return java.util.Collections.singletonList(new Local()); }', False),
856
+ ('public Object[] run() { Supplier<Object> value = () -> { return null; }; return new Object[]{value}; }', False),
857
+ ('public java.util.List<Object> run() { Runnable task = () -> {}; task.run(); return null; }', True),
858
+ ('public Object[] run() { class Local { Object value() { return null; } } use(new Local()); return null; }', True),
859
+ ])
860
+ def test_collection_null_returns_belong_to_the_declaring_function(body, blocked):
861
+ assert ("java:S1168" in _codes(dcs_rule_issues(_plugin(body)))) is blocked
862
+
863
+
864
+ @pytest.mark.parametrize("body,blocked", [
865
+ ('public String jdbc(java.sql.ResultSet result) { return result.getString(1); } public String json(com.alibaba.fastjson.JSONArray result) { return result.getString(0); }', False),
866
+ ('public String jdbc(java.sql.ResultSet result) { return result.getString(0); } public String json(com.alibaba.fastjson.JSONArray result) { return result.getString(1); }', True),
867
+ ('public String jdbc() { ResultSet result = query(); return result.getString(1); } public String json() { JSONArray result = parse(); return result.getString(0); }', False),
868
+ ('public String run(boolean ready) { if (ready) { ResultSet result = query(); use(result.getString(1)); } JSONArray result = parse(); return result.getString(0); }', False),
869
+ ('private ResultSet result; public String run(JSONArray result) { return result.getString(0); }', False),
870
+ ('private ResultSet result; public String run(JSONArray result) { return this.result.getString(0); }', True),
871
+ ('public String run() { return result.getString(0); } private ResultSet result;', True),
872
+ ('private ResultSet result; public Object run(Stream<JSONArray> values) { return values.map(result -> result.getString(0)); }', False),
873
+ ('public Object run(Stream<ResultSet> values) { return values.map((ResultSet result) -> { return result.getString(0); }); }', True),
874
+ ('public void run() { ResultSet[] results = query(); use(results); } public String parse(JSONArray results) { return results.getString(0); }', False),
875
+ ])
876
+ def test_jdbc_receiver_types_respect_lexical_bindings(body, blocked):
877
+ assert ("java:S2695" in _codes(dcs_rule_issues(_plugin(body)))) is blocked
878
+
879
+
880
+ def test_thread_and_catalogue_receivers_do_not_leak_across_methods():
881
+ source = _plugin('public void threaded(Thread worker) { use(worker); } public void plain(Object worker) { worker.notifyAll(); } '
882
+ 'public void query(QueryServiceHelper helper) { use(helper); } public void render(Renderer helper, Object[] rows) { for (Object row : rows) { helper.query(row); } }')
883
+ assert not {"java:S2236", "CVE-KD-0051-62"} & _codes(dcs_rule_issues(source))
884
+
885
+
886
+ @pytest.mark.parametrize("action", ["CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION"])
887
+ def test_foreign_key_delete_actions_are_not_delete_statements(action):
888
+ sql = f"CREATE TABLE t_child (fid bigint, parent_id bigint, FOREIGN KEY (parent_id) REFERENCES t_parent(fid) ON /* note */ DELETE\n{action});"
889
+ assert "CVE-KD-SQL-0007" not in _codes(scan_sql(sql.encode()))
890
+ real = sql + '\nDELETE t_child WHERE fid=1;\nDELETE FROM "t_child" WHERE fid=2;'
891
+ findings = [issue for issue in scan_sql(real.encode()) if "CVE-KD-SQL-0007" in issue["ruleCodes"]]
892
+ assert [issue["line"] for issue in findings] == [3, 4]
893
+
894
+
895
+ def test_new_catalogue_and_documentation_are_available_in_the_executed_locale():
896
+ assert Path(dcs_code_gate.__file__).resolve().parent == SCRIPTS
897
+ catalog = json.loads((SCRIPTS / "dcs-rule-catalog.json").read_text(encoding="utf-8"))
898
+ assert catalog == dcs_code_gate.CATALOG
899
+ guide = (SCRIPTS.parent / "references" / "dcs-code-rules.md").read_text(encoding="utf-8")
900
+ for value in ("金蝶AI套件公有云门禁20261026.xlsx", "扫描规则_1026144356.xlsx", "1467", "428", "313", "115", "1582", "relatedRuleCodes", "CVE-KD-0051-14…67", "CVE-KD-SQL-0017", "java:S4347"):
901
+ assert value in guide