create-dwy 0.5.1 → 0.6.1

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 (156) hide show
  1. package/bin/index.js +6 -15
  2. package/package.json +3 -3
  3. package/src/create.js +61 -37
  4. package/src/sync.js +206 -61
  5. package/templates/claude-global/CLAUDE.md +92 -0
  6. package/templates/claude-global/rules/dwy-android-core.md +610 -0
  7. package/templates/claude-global/rules/dwy-api-design.md +110 -0
  8. package/templates/claude-global/rules/dwy-db-migration.md +46 -0
  9. package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
  10. package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
  11. package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
  12. package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
  13. package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
  14. package/templates/claude-global/rules/dwy-git-security.md +53 -0
  15. package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
  16. package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
  17. package/templates/claude-global/rules/dwy-project-structure.md +181 -0
  18. package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +292 -358
  19. package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
  20. package/templates/claude-global/rules/dwy-python-orm.md +61 -0
  21. package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +99 -72
  22. package/templates/claude-global/rules/dwy-python-testing.md +53 -0
  23. package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
  24. package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
  25. package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
  26. package/templates/claude-global/rules/dwy-vue-api.md +146 -0
  27. package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +338 -152
  28. package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
  29. package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
  30. package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
  31. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
  32. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
  33. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
  34. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
  35. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
  36. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
  37. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
  38. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
  39. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
  40. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
  41. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
  42. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
  43. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
  44. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
  45. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
  46. package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
  47. package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
  48. package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
  49. package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
  50. package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
  51. package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
  52. package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
  53. package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
  54. package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
  55. package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
  56. package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
  57. package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
  58. package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
  59. package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
  60. package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
  61. package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
  62. package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
  63. package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
  64. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
  65. package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
  66. package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
  67. package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
  68. package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
  69. package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
  70. package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
  71. package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
  72. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
  73. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
  74. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
  75. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
  76. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
  77. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
  78. package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
  79. package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
  80. package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
  81. package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
  82. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
  83. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
  84. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
  85. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
  86. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
  87. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
  88. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
  89. package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
  90. package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
  91. package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
  92. package/templates/claude-global/skills/{dwy-release → dwy-publish}/SKILL.md +42 -18
  93. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
  94. package/templates/project/backend/.dwy.hbs +5 -0
  95. package/templates/project/backend/.python-version +1 -0
  96. package/templates/project/backend/CLAUDE.md.hbs +40 -0
  97. package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
  98. package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
  99. package/templates/project/backend/deploy.sh.hbs +18 -0
  100. package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
  101. package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
  102. package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
  103. package/templates/project/backend/src/app/database.py +10 -0
  104. package/templates/project/backend/src/app/dependencies.py +5 -0
  105. package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
  106. package/templates/project/backend/src/app/models/base.py +5 -0
  107. package/templates/project/web/CLAUDE.md.hbs +29 -36
  108. package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
  109. package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
  110. package/templates/project/web/apps/web/src/App.vue +9 -0
  111. package/templates/project/web/apps/web/src/api/client.ts +5 -0
  112. package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
  113. package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
  114. package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
  115. package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
  116. package/templates/project/web/apps/web/src/style.css +2 -0
  117. package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
  118. package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
  119. package/templates/project/web/apps/web/tsconfig.app.json +18 -0
  120. package/templates/project/web/apps/web/tsconfig.json +6 -0
  121. package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
  122. package/templates/project/web/package.json.hbs +16 -0
  123. package/templates/project/web/packages/.gitkeep +0 -0
  124. package/templates/project/web/pnpm-workspace.yaml +3 -0
  125. package/templates/claude-global/hooks/post-sync-claude-global.sh +0 -72
  126. package/templates/claude-global/rules/agent-team-execution.md +0 -138
  127. package/templates/claude-global/rules/agent-team-patterns.md +0 -157
  128. package/templates/claude-global/rules/git-commit-convention.md +0 -106
  129. package/templates/claude-global/rules/git-security.md +0 -144
  130. package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
  131. package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
  132. package/templates/project/web/.claude/commands/deploy.md +0 -4
  133. package/templates/project/web/backend/src/app/models/base.py +0 -5
  134. package/templates/project/web/deploy.sh.hbs +0 -113
  135. package/templates/project/web/frontend/src/App.vue +0 -3
  136. package/templates/project/web/frontend/src/api/client.ts +0 -13
  137. package/templates/project/web/frontend/src/style.css +0 -1
  138. package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
  139. package/templates/project/web/frontend/tsconfig.app.json +0 -14
  140. package/templates/project/web/frontend/tsconfig.json +0 -4
  141. /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
  142. /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
  143. /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
  144. /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
  145. /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
  146. /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
  147. /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
  148. /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
  149. /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
  150. /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
  151. /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
  152. /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
  153. /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
  154. /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
  155. /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
  156. /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env bash
2
+ # 凭证强度审计 (只读, 全脱敏)
3
+ #
4
+ # 检查范围 (7 类):
5
+ # 1. .env 文件: PASSWORD/SECRET/TOKEN/KEY/AK/SK 字段
6
+ # 2. docker 容器内联 env (Config.Env)
7
+ # 3. Redis 容器 --requirepass 启动参数
8
+ # 4. Postgres 容器 POSTGRES_PASSWORD
9
+ # 5. frps/frpc auth.token (toml/ini)
10
+ # 6. DolphinDB 配置文件 password
11
+ # 7. SSH 私钥 类型 + 位数 (~/.ssh/id_*)
12
+ #
13
+ # 强制脱敏: 只输出派生指标 (len/classes/strength), 永不输出明文
14
+ # 字典词: 内置 ~30 个最常见弱口令
15
+
16
+ TARGET="$1"; shift
17
+ SSH_OPTS=("$@")
18
+
19
+ ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
20
+ # ---------- 强度评估函数 ----------
21
+ # 输入: 一个秘密字符串(从 stdin)
22
+ # 输出: "len=N classes=K strength=STRONG/MEDIUM/WEAK reason=xxx"
23
+ # 不打印明文, 不写文件
24
+ strength_of() {
25
+ local val
26
+ IFS= read -r val
27
+ local len=${#val}
28
+
29
+ # 字典词内置(精简, 30 个最常见弱口令; 大小写不敏感子串匹配)
30
+ local weak_dict='password|passwd|123456|123123|qwerty|abc123|admin|administrator|root|test|demo|guest|user|welcome|letmein|changeme|secret|default|temp|public|private|master|sample|example|prod|dev|staging|backup|hello|monkey'
31
+ local is_dict=0
32
+ echo "$val" | grep -qiE "($weak_dict)" && is_dict=1
33
+
34
+ # 字符类
35
+ local has_upper=0 has_lower=0 has_digit=0 has_symbol=0
36
+ [[ "$val" =~ [A-Z] ]] && has_upper=1
37
+ [[ "$val" =~ [a-z] ]] && has_lower=1
38
+ [[ "$val" =~ [0-9] ]] && has_digit=1
39
+ [[ "$val" =~ [^a-zA-Z0-9] ]] && has_symbol=1
40
+ local classes=$(( has_upper + has_lower + has_digit + has_symbol ))
41
+
42
+ # 评级
43
+ local strength reason
44
+ if [[ "$len" -lt 8 ]]; then strength="WEAK"; reason="len<8"
45
+ elif [[ "$is_dict" == "1" ]]; then strength="WEAK"; reason="dict_match"
46
+ elif [[ "$len" -lt 16 ]]; then strength="MEDIUM"; reason="len<16"
47
+ elif [[ "$classes" -lt 3 ]]; then strength="MEDIUM"; reason="classes<3"
48
+ else strength="STRONG"; reason="ok"
49
+ fi
50
+ printf "len=%d classes=%d strength=%s reason=%s" "$len" "$classes" "$strength" "$reason"
51
+ }
52
+
53
+ # 排除明显是"数值/枚举配置参数"而非凭证的 key 名
54
+ # (子串匹配 TOKEN/KEY 会误命中 ACCESS_TOKEN_EXPIRE_MINUTES / KEY_TIMEOUT 这种)
55
+ is_config_not_secret() {
56
+ local key="$1"
57
+ echo "$key" | grep -qiE '(EXPIRE|EXPIRY|TTL|TIMEOUT|SECONDS|MINUTES|HOURS|DAYS|COUNT|LIMIT|RETRY|MAX|MIN|SIZE|LENGTH|INTERVAL|DELAY|ENABLED|DISABLED|MODE|TYPE|VERSION|PORT|HOST|URL|PATH|FILE|DIR)$|_(EXPIRE|TTL|TIMEOUT|SECONDS|MINUTES|HOURS|DAYS|COUNT|LIMIT|MAX|MIN|SIZE|MODE|TYPE)_'
58
+ }
59
+
60
+ # 包装: 给定 key+value, 加上"高敏字段长度门槛 32"判定
61
+ evaluate_secret() {
62
+ local key="$1"
63
+ local val="$2"
64
+ [[ -z "$val" ]] && { echo " (空值)"; return; }
65
+
66
+ # 判定是否真凭证字段 (key 名含 PASSWORD/SECRET/ACCESS_KEY_SECRET 等明确含义的)
67
+ # 这类即使值是纯数字短串(如 DolphinDB 默认 123456) 也必须评估为 WEAK 而非 SKIP
68
+ local is_true_credential=0
69
+ echo "$key" | grep -qiE '(PASSWORD|PASSWD|SECRET|PRIVATE[_-]?KEY|ACCESS[_-]?KEY[_-]?SECRET|_SK$|^SK_)' && is_true_credential=1
70
+
71
+ # Bug fix 1: 跳过明显是配置参数的 key (含 EXPIRE/TTL/TIMEOUT/MAX/MIN 等)
72
+ # 但真凭证字段不跳过
73
+ if [[ "$is_true_credential" == "0" ]] && is_config_not_secret "$key"; then
74
+ printf " %-30s [i] SKIP: 看起来是配置参数(非凭证), key 名含 EXPIRE/TTL/TIMEOUT/MAX 等\n" "$key"
75
+ return
76
+ fi
77
+ # Bug fix 1.b: 纯数字短值(像 30 / 86400 这种 TTL/expire) 跳过
78
+ # 但真凭证字段不跳过 (DolphinDB 默认 123456 / Redis 默认 123 这种弱密码必须报)
79
+ if [[ "$is_true_credential" == "0" ]] && [[ "$val" =~ ^[0-9]+$ ]] && [[ ${#val} -lt 10 ]]; then
80
+ printf " %-30s [i] SKIP: 纯数字短值(看起来是配置参数, len=%d)\n" "$key" "${#val}"
81
+ return
82
+ fi
83
+
84
+ # 是否高敏字段
85
+ local high_sens=0
86
+ echo "$key" | grep -qiE 'SECRET|JWT|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY[_-]?SECRET|PRIVATE[_-]?KEY|_AK$|_SK$|^AK_|^SK_' && high_sens=1
87
+
88
+ local result
89
+ result=$(printf '%s\n' "$val" | strength_of)
90
+ local len=$(echo "$result" | grep -oE 'len=[0-9]+' | cut -d= -f2)
91
+ local classes=$(echo "$result" | grep -oE 'classes=[0-9]+' | cut -d= -f2)
92
+ local strength=$(echo "$result" | grep -oE 'strength=[A-Z]+' | cut -d= -f2)
93
+
94
+ # Bug fix 2: 长度 >= 24 即视为 STRONG (即使 classes 少, 熵足够)
95
+ # 例如 48 字符 base64 / 64 字符 hex 即使只 2 类, 熵 >= 192 bits 远超 128 bits 安全门槛
96
+ if [[ "$strength" != "WEAK" && "$len" -ge 24 ]]; then
97
+ strength="STRONG"
98
+ result="${result/strength=MEDIUM/strength=STRONG}"
99
+ result="${result/reason=classes<3/reason=long_enough}"
100
+ result="${result/reason=len<16/reason=long_enough}"
101
+ fi
102
+
103
+ printf " %-30s %s" "$key" "$result"
104
+ # 严重级标记
105
+ if [[ "$strength" == "WEAK" ]]; then
106
+ printf " [!!!] CRITICAL: 弱凭证, 必须立即轮换\n"
107
+ elif [[ "$high_sens" == "1" && "$len" -lt 32 ]]; then
108
+ printf " [!!] HIGH: 高敏字段建议 >= 32 字符随机串\n"
109
+ elif [[ "$strength" == "MEDIUM" ]]; then
110
+ printf " [!] MEDIUM: 建议加长至 16+ 且 3 类字符\n"
111
+ else
112
+ printf " [OK]\n"
113
+ fi
114
+ }
115
+
116
+ # ---------- 1. .env 文件扫描 ----------
117
+ echo "########## 1. .env 文件凭证强度 ##########"
118
+ ENV_FILES=$(sudo -n find /home /opt /srv /root -maxdepth 6 -type f \
119
+ \( -name ".env" -o -name ".env.*" -o -name "*.env" \) 2>/dev/null \
120
+ | grep -vE '\.example|\.template|\.sample|\.bak|\.old|node_modules' \
121
+ | sort -u | head -20)
122
+ [[ -z "$ENV_FILES" ]] && ENV_FILES=$(find /home /opt /srv /root -maxdepth 6 -type f \
123
+ \( -name ".env" -o -name ".env.*" -o -name "*.env" \) 2>/dev/null \
124
+ | grep -vE '\.example|\.template|\.sample|\.bak|\.old|node_modules' \
125
+ | sort -u | head -20)
126
+
127
+ if [[ -z "$ENV_FILES" ]]; then
128
+ echo "[i] 未找到 .env 文件"
129
+ else
130
+ for f in $ENV_FILES; do
131
+ echo ""
132
+ echo "[$f]"
133
+ # 只挑密码类字段, 解析 KEY=VALUE
134
+ if ! sudo -n cat "$f" 2>/dev/null | head -1 >/dev/null && ! cat "$f" 2>/dev/null | head -1 >/dev/null; then
135
+ echo " [i] 不可读"
136
+ continue
137
+ fi
138
+ (sudo -n cat "$f" 2>/dev/null || cat "$f" 2>/dev/null) \
139
+ | grep -E '^[[:space:]]*[A-Z][A-Z0-9_]*[[:space:]]*=' \
140
+ | grep -iE '(PASSWORD|PASSWD|SECRET|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|_AK|_SK|^AK_|^SK_)' \
141
+ | while IFS='=' read -r key val; do
142
+ key=$(echo "$key" | tr -d ' \t')
143
+ # 去引号 + 行尾注释
144
+ val=$(echo "$val" | sed -E 's/^[[:space:]]*//; s/[[:space:]]*#.*$//; s/^"//; s/"$//; s/^'\''//; s/'\''$//')
145
+ evaluate_secret "$key" "$val"
146
+ done
147
+ done
148
+ fi
149
+
150
+ # ---------- 2. docker 容器内联 env ----------
151
+ echo ""
152
+ echo "########## 2. Docker 容器内联 env 凭证强度 ##########"
153
+ if command -v docker >/dev/null 2>&1; then
154
+ for c in $(docker ps -q 2>/dev/null); do
155
+ name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
156
+ echo ""
157
+ echo "[$name]"
158
+ docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$c" 2>/dev/null \
159
+ | grep -iE '(PASSWORD|PASSWD|SECRET|TOKEN|API[_-]?KEY|ACCESS[_-]?KEY|PRIVATE[_-]?KEY|_AK=|_SK=|^AK_|^SK_)' \
160
+ | while IFS='=' read -r key val; do
161
+ key=$(echo "$key" | tr -d ' \t')
162
+ evaluate_secret "$key" "$val"
163
+ done
164
+ done
165
+ else
166
+ echo "[i] Docker 未安装"
167
+ fi
168
+
169
+ # ---------- 3. Redis requirepass (从启动参数提取) ----------
170
+ echo ""
171
+ echo "########## 3. Redis --requirepass 强度 ##########"
172
+ if command -v docker >/dev/null 2>&1; then
173
+ REDIS_FOUND=0
174
+ for c in $(docker ps -q 2>/dev/null); do
175
+ image=$(docker inspect -f '{{.Config.Image}}' "$c" 2>/dev/null)
176
+ if echo "$image" | grep -qi "redis"; then
177
+ REDIS_FOUND=1
178
+ name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
179
+ echo ""
180
+ echo "[$name]"
181
+ args=$(docker inspect -f '{{range .Args}}{{.}} {{end}}' "$c" 2>/dev/null)
182
+ pass=$(echo "$args" | grep -oE -- '--requirepass [^ ]+' | head -1 | awk '{print $2}')
183
+ if [[ -n "$pass" ]]; then
184
+ evaluate_secret "REDIS_PASSWORD(--requirepass)" "$pass"
185
+ else
186
+ echo " [!!!] CRITICAL: Redis 未设置 --requirepass"
187
+ fi
188
+ fi
189
+ done
190
+ [[ "$REDIS_FOUND" == "0" ]] && echo "[i] 未发现 Redis 容器"
191
+ fi
192
+
193
+ # ---------- 4. Postgres POSTGRES_PASSWORD (容器 env, 已在 2 覆盖, 此处单独 highlight) ----------
194
+ echo ""
195
+ echo "########## 4. Postgres POSTGRES_PASSWORD 强度 ##########"
196
+ if command -v docker >/dev/null 2>&1; then
197
+ PG_FOUND=0
198
+ for c in $(docker ps -q 2>/dev/null); do
199
+ image=$(docker inspect -f '{{.Config.Image}}' "$c" 2>/dev/null)
200
+ if echo "$image" | grep -qi "postgres"; then
201
+ PG_FOUND=1
202
+ name=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null | sed 's|^/||')
203
+ echo ""
204
+ echo "[$name]"
205
+ pass=$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$c" 2>/dev/null \
206
+ | grep '^POSTGRES_PASSWORD=' | head -1 | cut -d= -f2-)
207
+ if [[ -n "$pass" ]]; then
208
+ evaluate_secret "POSTGRES_PASSWORD" "$pass"
209
+ else
210
+ echo " [!!!] CRITICAL: 未取得 POSTGRES_PASSWORD (是否 secrets 挂载或 trust 认证?)"
211
+ fi
212
+ fi
213
+ done
214
+ [[ "$PG_FOUND" == "0" ]] && echo "[i] 未发现 Postgres 容器"
215
+ fi
216
+
217
+ # ---------- 5. frp auth.token ----------
218
+ echo ""
219
+ echo "########## 5. frps/frpc auth.token 强度 ##########"
220
+ FRP_FILES=$(sudo -n find /etc /opt /home /root -maxdepth 6 -type f \
221
+ \( -name "frps.toml" -o -name "frpc.toml" -o -name "frps.ini" -o -name "frpc.ini" \) 2>/dev/null \
222
+ | grep -vE '\.bak|\.old|\.example' | sort -u | head -10)
223
+ [[ -z "$FRP_FILES" ]] && FRP_FILES=$(find /etc /opt /home /root -maxdepth 6 -type f \
224
+ \( -name "frps.toml" -o -name "frpc.toml" -o -name "frps.ini" -o -name "frpc.ini" \) 2>/dev/null \
225
+ | grep -vE '\.bak|\.old|\.example' | sort -u | head -10)
226
+
227
+ if [[ -z "$FRP_FILES" ]]; then
228
+ echo "[i] 未发现 frp 配置文件"
229
+ else
230
+ for f in $FRP_FILES; do
231
+ echo ""
232
+ echo "[$f]"
233
+ # 提 token = "xxx" / token = xxx / auth.token = xxx
234
+ # 注意: 必须用 { ...; } | grep 而非 cat || cat | grep
235
+ # 后者因 | 优先级高于 ||, 当 sudo 成功时只跑 sudo cat, 后续 pipeline 被丢弃,
236
+ # 导致 token 变量 = 整个文件内容, evaluate_secret 用 read 取第一行 (常是 bindPort=7000)
237
+ token=$( { sudo -n cat "$f" 2>/dev/null || cat "$f" 2>/dev/null; } \
238
+ | grep -iE '^[[:space:]]*(auth\.)?token[[:space:]]*=' \
239
+ | head -1 | sed -E 's/.*=//; s/^[[:space:]]*//; s/[[:space:]]*$//; s/^"//; s/"$//; s/^'\''//; s/'\''$//')
240
+ if [[ -n "$token" ]]; then
241
+ evaluate_secret "frp.token" "$token"
242
+ else
243
+ echo " [!!] HIGH: 未配 token (frp 无认证, 任何人可注册到此 frps)"
244
+ fi
245
+ done
246
+ fi
247
+
248
+ # ---------- 6. DolphinDB 密码 ----------
249
+ echo ""
250
+ echo "########## 6. DolphinDB 配置密码强度 ##########"
251
+ DDB_FILES=$(sudo -n find /etc /opt /home /root -maxdepth 6 -type f \
252
+ \( -name "dolphindb.cfg" -o -name "cluster.cfg" -o -name "controller.cfg" -o -name "agent.cfg" \) 2>/dev/null \
253
+ | grep -vE '\.bak|\.old|\.example' | sort -u | head -10)
254
+ [[ -z "$DDB_FILES" ]] && DDB_FILES=$(find /etc /opt /home /root -maxdepth 6 -type f \
255
+ \( -name "dolphindb.cfg" -o -name "cluster.cfg" -o -name "controller.cfg" -o -name "agent.cfg" \) 2>/dev/null \
256
+ | grep -vE '\.bak|\.old|\.example' | sort -u | head -10)
257
+
258
+ if [[ -z "$DDB_FILES" ]]; then
259
+ echo "[i] 未发现 DolphinDB 配置文件"
260
+ else
261
+ for f in $DDB_FILES; do
262
+ echo ""
263
+ echo "[$f]"
264
+ # DolphinDB 用户密码通常在 users.cfg 或通过 createUser 写入, 这里只看配置文件里的
265
+ # adminPassword / password / encryptedPassword 等字段
266
+ found=0
267
+ while IFS='=' read -r key val; do
268
+ key=$(echo "$key" | tr -d ' \t')
269
+ val=$(echo "$val" | sed -E 's/^[[:space:]]*//; s/[[:space:]]*$//; s/^"//; s/"$//')
270
+ [[ -n "$val" ]] && { evaluate_secret "$key" "$val"; found=1; }
271
+ done < <(sudo -n grep -iE '(password|passwd|admin[Pp]ass)' "$f" 2>/dev/null \
272
+ || grep -iE '(password|passwd|admin[Pp]ass)' "$f" 2>/dev/null)
273
+ [[ "$found" == "0" ]] && echo " [i] 配置文件中未直接含密码字段(可能用 users.cfg 或 createUser 写入)"
274
+ done
275
+ fi
276
+
277
+ # ---------- 7. SSH 私钥强度 ----------
278
+ echo ""
279
+ echo "########## 7. SSH 私钥类型 / 位数 ##########"
280
+ # 当前用户 + root 的 ~/.ssh/ (Bug fix 3: 去重, root 用户登录时 $HOME=/root 会重复)
281
+ SSH_DIRS=$(printf '%s\n' "$HOME/.ssh" /root/.ssh /home/*/.ssh 2>/dev/null | sort -u)
282
+ for ssh_dir in $SSH_DIRS; do
283
+ [[ -d "$ssh_dir" ]] || continue
284
+ for kf in "$ssh_dir"/id_rsa "$ssh_dir"/id_ed25519 "$ssh_dir"/id_ecdsa "$ssh_dir"/id_dsa; do
285
+ [[ -f "$kf" ]] || continue
286
+ info=$(ssh-keygen -l -f "$kf" 2>/dev/null || sudo -n ssh-keygen -l -f "$kf" 2>/dev/null)
287
+ if [[ -z "$info" ]]; then
288
+ echo " $kf [i] 无法读取(权限?)"
289
+ continue
290
+ fi
291
+ bits=$(echo "$info" | awk '{print $1}')
292
+ type=$(echo "$info" | grep -oE '\([A-Z0-9]+\)$' | tr -d '()')
293
+ perm=$(stat -c '%a' "$kf" 2>/dev/null)
294
+
295
+ case "$type" in
296
+ ED25519)
297
+ printf " %-40s type=%-8s bits=%-4s perm=%s [OK]\n" "$kf" "$type" "$bits" "$perm" ;;
298
+ RSA)
299
+ if [[ "$bits" -ge 4096 ]]; then mark="[OK]"
300
+ elif [[ "$bits" -ge 3072 ]]; then mark="[OK] (建议升 ed25519)"
301
+ elif [[ "$bits" -ge 2048 ]]; then mark="[!] MEDIUM: RSA-${bits} 可接受但建议升 ed25519 或 RSA-4096"
302
+ else mark="[!!!] CRITICAL: RSA-${bits} 已不安全, 立即轮换"
303
+ fi
304
+ printf " %-40s type=%-8s bits=%-4s perm=%s %s\n" "$kf" "$type" "$bits" "$perm" "$mark" ;;
305
+ ECDSA)
306
+ printf " %-40s type=%-8s bits=%-4s perm=%s [!] MEDIUM: ECDSA 存疑(NIST 曲线), 建议 ed25519\n" "$kf" "$type" "$bits" "$perm" ;;
307
+ DSA)
308
+ printf " %-40s type=%-8s bits=%-4s perm=%s [!!!] CRITICAL: DSA 已废弃, 立即轮换\n" "$kf" "$type" "$bits" "$perm" ;;
309
+ *)
310
+ printf " %-40s type=%-8s bits=%-4s perm=%s\n" "$kf" "$type" "$bits" "$perm" ;;
311
+ esac
312
+
313
+ # 权限检查
314
+ if [[ -n "$perm" && "$perm" != "600" && "$perm" != "400" ]]; then
315
+ echo " [!!] HIGH: 私钥权限 $perm 过宽, 应 600"
316
+ fi
317
+ done
318
+ done
319
+
320
+ echo ""
321
+ echo "########## 凭证强度汇总 ##########"
322
+ echo "[i] 修复指引:"
323
+ echo " - WEAK 密码: 立即轮换 + 走 dwy-server-security 规则 (>= 16 字符 + 三类字符)"
324
+ echo " - 高敏字段 (JWT/SECRET/AK/SK): >= 32 字符随机串"
325
+ echo " - 生成命令: openssl rand -base64 24 | tr -d '=+/' | cut -c1-20"
326
+ echo " - 高敏: openssl rand -hex 32 (生成 64 字符 hex token)"
327
+ REMOTE
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env bash
2
+ # 依赖服务连通性 / 防火墙
3
+ TARGET="$1"; shift
4
+ SSH_OPTS=("$@")
5
+
6
+ ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
7
+ echo "--- 防火墙状态 ---"
8
+ if command -v ufw >/dev/null 2>&1; then
9
+ echo "[ufw]"
10
+ sudo -n ufw status verbose 2>/dev/null || echo " [!] 需 sudo 查看 ufw"
11
+ fi
12
+ if command -v firewall-cmd >/dev/null 2>&1; then
13
+ echo "[firewalld]"
14
+ sudo -n firewall-cmd --state 2>/dev/null
15
+ sudo -n firewall-cmd --list-all 2>/dev/null
16
+ fi
17
+ echo "[iptables]"
18
+ sudo -n iptables -L INPUT -n --line-numbers 2>/dev/null | head -20 || echo " [i] 无法读取 iptables"
19
+
20
+ echo ""
21
+ echo "--- 监听端口 (按地址分组) ---"
22
+ (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | awk 'NR>1 {print $4}' | awk -F: '{
23
+ port=$NF;
24
+ ip=substr($0,1,length($0)-length(port)-1);
25
+ if (ip=="0.0.0.0" || ip=="*" || ip=="[::]") public[port]++;
26
+ else if (ip=="127.0.0.1" || ip=="[::1]") loop[port]++;
27
+ else internal[port]++;
28
+ }
29
+ END {
30
+ print "公网监听 (0.0.0.0 / *):"
31
+ for (p in public) print " " p
32
+ print "内网监听 (具体 IP):"
33
+ for (p in internal) print " " p
34
+ print "回环监听 (127.0.0.1):"
35
+ for (p in loop) print " " p
36
+ }'
37
+
38
+ echo ""
39
+ echo "--- 系统负载与内存 ---"
40
+ uptime
41
+ free -h 2>/dev/null | head -3
42
+
43
+ echo ""
44
+ echo "--- 磁盘 ---"
45
+ df -h | grep -vE "tmpfs|udev" | head -10
46
+
47
+ echo ""
48
+ echo "--- 已安装的 Web 应用进程 (常见) ---"
49
+ ps -eo user,pid,%cpu,%mem,cmd --sort=-%mem 2>/dev/null \
50
+ | grep -E "(nginx|node|python|uvicorn|gunicorn|java|php-fpm|redis-server|postgres|mysql|mongod)" \
51
+ | grep -v grep | head -20
52
+
53
+ echo ""
54
+ echo "--- 应用健康检查端点 (常见路径自测) ---"
55
+ NGINX_DUMP=$(sudo -n nginx -T 2>/dev/null || nginx -T 2>/dev/null || true)
56
+ DOMAINS=$(echo "$NGINX_DUMP" | awk '/server_name/ {for(i=2;i<=NF;i++) print $i}' | tr -d ';' | sort -u | grep -v "^_$" | head -3)
57
+ [[ -z "$DOMAINS" ]] && DOMAINS="localhost"
58
+ for D in $DOMAINS; do
59
+ for path in "/health" "/api/health" "/healthz" "/_health" "/status"; do
60
+ code=$(curl -k -sS -o /dev/null -w "%{http_code}" --max-time 3 "https://$D$path" 2>/dev/null || echo "000")
61
+ [[ "$code" != "404" && "$code" != "000" ]] && printf " https://%s%s -> %s\n" "$D" "$path" "$code"
62
+ done
63
+ done
64
+
65
+ echo ""
66
+ echo "--- 系统更新提示 ---"
67
+ if command -v apt >/dev/null 2>&1; then
68
+ sudo -n apt list --upgradable 2>/dev/null | head -10 || echo " [i] 无法检查 apt 更新"
69
+ elif command -v yum >/dev/null 2>&1; then
70
+ sudo -n yum check-update 2>/dev/null | head -10 || echo " [i] 无法检查 yum 更新"
71
+ fi
72
+
73
+ echo ""
74
+ echo "--- 内核/系统启动时间 ---"
75
+ who -b 2>/dev/null
76
+ echo "kernel: $(uname -r)"
77
+ REMOTE
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # SSH 安全检查 (只读)
3
+ TARGET="$1"; shift
4
+ SSH_OPTS=("$@")
5
+
6
+ ssh "${SSH_OPTS[@]}" "${TARGET}" bash -s <<'REMOTE'
7
+ echo "--- /etc/ssh/sshd_config (filtered key directives) ---"
8
+ SSHD_CONF="/etc/ssh/sshd_config"
9
+ SSHD_DIR="/etc/ssh/sshd_config.d"
10
+ if [[ -r "$SSHD_CONF" ]]; then
11
+ grep -Ei "^[[:space:]]*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|PermitEmptyPasswords|Port|Protocol|MaxAuthTries|LoginGraceTime|AllowUsers|AllowGroups|DenyUsers|DenyGroups|ChallengeResponseAuthentication|UsePAM|X11Forwarding|ClientAliveInterval|ClientAliveCountMax|LogLevel|Banner)" "$SSHD_CONF" 2>/dev/null
12
+ if [[ -d "$SSHD_DIR" ]]; then
13
+ echo "--- $SSHD_DIR/*.conf ---"
14
+ grep -EiH "^[[:space:]]*(PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|PermitEmptyPasswords|Port|Protocol|MaxAuthTries)" "$SSHD_DIR"/*.conf 2>/dev/null
15
+ fi
16
+ else
17
+ echo "[!] $SSHD_CONF 不可读 (需要 sudo)"
18
+ fi
19
+
20
+ echo ""
21
+ echo "--- effective sshd config (sshd -T, 需 sudo) ---"
22
+ sudo -n sshd -T 2>/dev/null | grep -Ei "^(permitrootlogin|passwordauthentication|pubkeyauthentication|permitemptypasswords|port|protocol|maxauthtries|logingracetime|allowusers|allowgroups|x11forwarding|loglevel)" || echo "[i] 无法读取 effective config (需要 sudo 或 sshd -T 不可用)"
23
+
24
+ echo ""
25
+ echo "--- ssh process & version ---"
26
+ sshd -V 2>&1 | head -2 || ssh -V 2>&1
27
+
28
+ echo ""
29
+ echo "--- recent failed logins (last 20) ---"
30
+ sudo -n journalctl _COMM=sshd --since "7 days ago" 2>/dev/null | grep -Ei "failed|invalid" | tail -20 \
31
+ || sudo -n grep -Ei "failed|invalid" /var/log/auth.log 2>/dev/null | tail -20 \
32
+ || echo "[i] 无法读取认证日志 (需要 sudo)"
33
+
34
+ echo ""
35
+ echo "--- fail2ban status ---"
36
+ if command -v fail2ban-client >/dev/null 2>&1; then
37
+ sudo -n fail2ban-client status 2>/dev/null || echo "[i] fail2ban 已安装,但需要 sudo 查看状态"
38
+ else
39
+ echo "[!] fail2ban 未安装"
40
+ fi
41
+
42
+ echo ""
43
+ echo "--- authorized_keys 概览 (当前用户) ---"
44
+ if [[ -r ~/.ssh/authorized_keys ]]; then
45
+ awk '{print " key: " $1 " " substr($2,1,16) "... " $3}' ~/.ssh/authorized_keys
46
+ echo " total: $(wc -l < ~/.ssh/authorized_keys) keys"
47
+ else
48
+ echo "[i] 当前用户无 authorized_keys"
49
+ fi
50
+
51
+ echo ""
52
+ echo "--- /root/.ssh/authorized_keys (需 sudo) ---"
53
+ sudo -n test -f /root/.ssh/authorized_keys 2>/dev/null && \
54
+ echo " [!] root 用户存在 authorized_keys, 共 $(sudo -n wc -l < /root/.ssh/authorized_keys 2>/dev/null) 行" \
55
+ || echo "[i] 无 root authorized_keys 或无 sudo 权限"
56
+ REMOTE
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bash
2
+ # dwy-deploy-audit: 一键全量巡检(subagent 不可用时的串行/并行退化方案)
3
+ #
4
+ # 优先做法是让 Claude 用 subagent 并行派遣多个 Agent 跑各类 check_xxx.sh
5
+ # (见 SKILL.md Step 4)。本脚本仅作为本地一键回退使用。
6
+ #
7
+ # 用法:
8
+ # ./run_all.sh <user>@<host> [-p <port>] [-i <key>] [--serial]
9
+ #
10
+ # 默认 8 路并行。--serial 强制串行。
11
+
12
+ set -uo pipefail
13
+
14
+ if [[ $# -lt 1 ]]; then
15
+ echo "Usage: $0 <user>@<host|alias> [-p <port>] [-i <key>] [--serial]" >&2
16
+ exit 1
17
+ fi
18
+
19
+ TARGET="$1"; shift
20
+ SSH_OPTS=(-o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
21
+ PARALLEL=true
22
+
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ -p) SSH_OPTS+=(-p "$2"); shift 2;;
26
+ -i) SSH_OPTS+=(-i "$2"); shift 2;;
27
+ --serial) PARALLEL=false; shift;;
28
+ *) echo "Unknown arg: $1" >&2; exit 1;;
29
+ esac
30
+ done
31
+
32
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+ TMP_DIR=$(mktemp -d -t dwy-audit-XXXXXX)
34
+ trap 'rm -rf "$TMP_DIR"' EXIT
35
+
36
+ # 检查项: <section_name>:<script_name>
37
+ SECTIONS=(
38
+ "BASE_INFO:check_base.sh"
39
+ "SSH:check_ssh.sh"
40
+ "NGINX:check_nginx.sh"
41
+ "DB:check_db.sh"
42
+ "HTTPS:check_https.sh"
43
+ "ENV:check_env.sh"
44
+ "DOCKER:check_docker.sh"
45
+ "SERVICES:check_services.sh"
46
+ "RESILIENCE:check_resilience.sh"
47
+ "LOGS:check_logs.sh"
48
+ "CAPACITY:check_capacity.sh"
49
+ "SECRETS:check_secrets.sh"
50
+ )
51
+
52
+ echo "========================================"
53
+ echo " dwy-deploy-audit | target: ${TARGET}"
54
+ echo " timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
55
+ echo " mode: $([ "$PARALLEL" = true ] && echo "parallel (${#SECTIONS[@]} workers)" || echo 'serial')"
56
+ echo "========================================"
57
+
58
+ run_section() {
59
+ local name="$1"
60
+ local script="$2"
61
+ local out_file="${TMP_DIR}/${name}.out"
62
+ {
63
+ echo "########## SECTION: ${name} ##########"
64
+ bash "${SCRIPT_DIR}/${script}" "${TARGET}" "${SSH_OPTS[@]}" 2>&1 \
65
+ || echo "[!] ${name} 部分失败"
66
+ echo "########## END: ${name} ##########"
67
+ } > "$out_file" 2>&1
68
+ }
69
+
70
+ if [[ "$PARALLEL" == "true" ]]; then
71
+ # 并行:所有 section 后台启动
72
+ pids=()
73
+ for entry in "${SECTIONS[@]}"; do
74
+ name="${entry%%:*}"
75
+ script="${entry##*:}"
76
+ run_section "$name" "$script" &
77
+ pids+=($!)
78
+ done
79
+
80
+ # 等所有完成
81
+ for pid in "${pids[@]}"; do
82
+ wait "$pid"
83
+ done
84
+
85
+ # 按顺序输出结果(保证报告分类有序)
86
+ for entry in "${SECTIONS[@]}"; do
87
+ name="${entry%%:*}"
88
+ echo ""
89
+ cat "${TMP_DIR}/${name}.out"
90
+ done
91
+ else
92
+ # 串行
93
+ for entry in "${SECTIONS[@]}"; do
94
+ name="${entry%%:*}"
95
+ script="${entry##*:}"
96
+ echo ""
97
+ run_section "$name" "$script"
98
+ cat "${TMP_DIR}/${name}.out"
99
+ done
100
+ fi
101
+
102
+ echo ""
103
+ echo "========================================"
104
+ echo " 巡检数据采集完成,请由 Claude 分析生成报告"
105
+ echo "========================================"