24 Commits
Author SHA1 Message Date
風雪 0084c4f053 fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
fix(install): always refresh all lifecycle scripts on update
- Force-install 3m-ui-bin, install/update/uninstall.sh, 3m-ui.sh, 3m-ui
- Re-install /usr/local/bin/3m-ui from the same release (no stale entry)
- Log each installed path; verify executables after write
- chmod scripts in prepare_release; document in update.sh
2026-09-14 11:53:36 +08:00
freephilx 2e6256fb6a feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
feat(distribution): add complete installation and recovery workflows
Ship stable-only Linux bundles and multiarch Docker images with a pinned Mihomo core. Share secure first-run initialization across native and container installs.
Validate downloads before downtime, snapshot persistent state before updates, and restore the previous installation after a failed upgrade. Add backup, restore, healthcheck, and randomized administrator reset commands.
Add release verification, lifecycle regression tests, and installation documentation.
2026-09-08 22:47:35 +08:00
Z User db08c0745c fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
fix(scripts): quiet Mihomo download; try asset name variants; stable tag for mihomo
The curl 404 during 3m-ui update was optional Mihomo core refresh
(wrong/pre tag or missing -compatible asset). Panel update already succeeded.
2026-09-07 16:33:48 +00:00
Z User b7f0389353 ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
ci(test): single rolling Pre-release tag 'pre' (overwrite, no duplicates)
2026-09-06 15:57:03 +00:00
Z User 04865db275 feat(test): PWA manifest; update.sh HTTP health probe before success 2026-09-06 15:23:08 +00:00
Z User db96350bd8 fix(scripts): clean version argument case pattern 2026-09-06 15:06:00 +00:00
Z User 8086e729c0 fix(scripts): dedupe version arg pattern after test-* support 2026-09-06 15:05:51 +00:00
Z User f4bb43ecd3 ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
ci(test): publish Pre-releases; install/update prefer newest prerelease
2026-09-06 15:05:44 +00:00
Z User 1f484b8c06 feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
feat(p1-p3): safe config publish, batch user ops, cluster push, update harden, settings icons
- Config: generate no longer applies; Apply accepts editor YAML; Rollback UI
- Users batch: extend-days and add-traffic (GiB)
- Cluster: POST push-node with dry-run then create disabled on remote
- Install: NONINTERACTIVE result file; update: longer health poll, strip more seccomp
- Settings sidebar icons softer/outlined set
2026-09-05 15:22:27 +00:00
Z User 6102d9cf55 fix(systemd): drop SystemCallFilter causing SIGSYS (status=31) on pure-Go binary 2026-09-04 15:13:53 +00:00
Z User c6e33de929 fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
fix(update,tls): wait for service health; hydrate certs on boot without re-save
- update.sh: poll up to 15s before rollback (fixes false failure after binary update)
- On startup, restore listener PEMs from /var/lib/3m-ui/listener-certs and
fall back to names in existing mihomo config.yaml into DB before generate
2026-09-04 15:02:31 +00:00
Z User d6e64ab313 chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
chore(release): set version to v1.0.0
Align Makefile, embedded default, frontend package, and script help
text with the first stable release tag.
2026-08-31 06:21:47 +00:00
Z User 7addcdc5c7 fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
fix(non-listener): enforcement retry, DB restore warning, security headers, config placeholder, sensitive key gaps, dead UI options, bot API server, update.sh dedup
Non-listener comprehensive audit found 4 Medium + 4 Low issues.
All fixed. VLESS+REALITY and listener schema untouched.
== Medium ==
M-NEW-C1: Enforcement doesn't retry failed config regen
traffic/enforcement.go CheckAndEnforce updated lastBlocked BEFORE
calling RegenerateConfig. If regen failed, lastBlocked was already
set, so next tick saw no change and never retried — over-quota
users stayed connected. Fix: update lastBlocked only after
successful regen; preserve old state on failure for retry.
M-NEW-C2: DB restore orphans running process's SQLite handle
system/api.go RestoreDatabase renames the DB file, but the running
panel keeps writing to the old (unlinked) inode. Added prominent
[WARNING] log + restart_required:true + *** RESTART REQUIRED ***
message in response. In-place GORM reconnect not feasible; restart
is the correct approach.
M-NEW-G1: Backend doesn't send CSP/security HTTP headers
CSP was only in index.html meta tag. Added SecurityHeaders()
middleware setting X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, Referrer-Policy: same-origin,
X-XSS-Protection: 1; mode=block on all responses. CSP NOT set
on API responses (meta CSP handles the SPA shell).
M-NEW-A1: Default config.yaml has placeholder secrets
backend/config/config.yaml contained DefaultJWTSecret and
DefaultCredentialKey (rejected by Validate → startup failure for
developers). Replaced with CHANGE_ME placeholders + comments
explaining use install.sh or openssl rand -hex 32.
== Low ==
L-NEW-B1: isSensitiveSettingKey heuristic gaps
panel_settings.go blocklist missing 'key' and 'auth' substrings.
Added (api deliberately excluded — too broad).
L-NEW-D1: enabled_events UI has dead options
Settings.tsx Select included node_down/node_up/block/unblock which
have no backend effect. Removed. Kept only implemented events:
login/cpu/memory/crash/expiry/traffic.
L-NEW-D3: Bot loop hardcodes api.telegram.org
bot.go getUpdates/deleteWebhook hardcoded the official Telegram API
URL. Now takes apiBase parameter; loop() passes tgClient.apiBase()
so custom api_server setting is respected for long-polling.
L-NEW-H1: update.sh duplicate CONFIG_DIR
Line 11-12 had CONFIG_DIR defined twice. Removed duplicate.
== Verification ==
- go build ./... : PASS
- go vet ./... : PASS
- go test ./... : 16 packages all PASS
- npm run build : PASS
- gofmt : clean
- sh -n update.sh : OK
2026-08-30 11:13:08 +00:00
Z User dbec0b3099 fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
fix(scripts): install/update/uninstall port handling
Three script-level port UX issues found and fixed:
== install.sh: re-install with PANEL_PORT now updates existing config ==
write_config previously returned early when config.yaml already existed,
ignoring PANEL_PORT env var. Re-running 'PANEL_PORT=9000 bash install.sh'
on an existing install silently kept the old port.
Fix: when config.yaml exists and PANEL_PORT/THREE_M_UI_PORT is explicitly
set, sed-update the port/listen/public_url in the existing file. First-
time install (no config.yaml) still writes full config with random keys.
== update.sh: show panel port after successful update ==
Update completed with no indication of which port the panel is on.
Users had to check config.yaml manually.
Fix: after '[5/5] Update completed', read server.port from config.yaml
and display:
Panel: http://SERVER_IP:<port>/
(to change port: 3m-ui config port <new-port>)
Also added CONFIG_DIR constant (was missing).
== uninstall.sh: show previous panel port before removing config ==
Uninstall removed config without telling the user what port was in use.
Fix: before rm -rf $CONFIG_DIR, read and display:
Previous panel port: <port>
2026-08-30 09:47:16 +00:00
風雪 7bc01e0298 fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
fix(installers): Mihomo checksum via GitHub API digest, best-effort by default
The previous verify_mihomo_sha256 tried to download a SHA256SUMS file
from MetaCubeX/mihomo releases. Mihomo does NOT publish such a file
(verified across v1.17.0 — v1.19.30, all 404), so every install that
reached the Mihomo step failed with:
'Error: Mihomo checksum verification failed.
Set THREE_M_UI_INSECURE=1 to bypass (NOT recommended).'
Two-part fix:
1. Use GitHub's Release API per-asset 'digest' field instead of a
SHA256SUMS file. GitHub computes sha256 server-side when an asset
is uploaded, so the digest is authoritative even though Mihomo
itself publishes no checksums. Added github_asset_digest() helper
that queries /repos/{repo}/releases/tags/{tag} and parses the
matching asset's digest field (no jq dependency, pure awk).
2. Make Mihomo verification best-effort by default. Anonymous GitHub
API is rate-limited to 60 requests/hour/IP, so hard-failing would
break installs for anyone sharing a NAT IP or after a few retries.
The fail-closed guarantee already covers our own 3m-ui binary
(SHA256SUMS + cosign). Mihomo integrity is additionally protected
by HTTPS transport + the ELF smoke-test.
Users who want strict Mihomo verification can set
THREE_M_UI_VERIFY_MIHOMO=1 to make verification failures abort
(requires GitHub API to be reachable).
Threat model unchanged: our binary is fully verified end-to-end;
Mihomo is an independent project we can only best-effort verify.
The HTTPS download to GitHub + ELF check catches the realistic
threats (network MITM, truncated download, wrong file).
2026-08-29 11:00:34 +00:00
風雪 8d730a2d08 fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
fix(installers): cosign verify accepts tag-ref or main-branch identity
The back-filled v1.0.0 was signed via workflow_dispatch (ref=main), so
its OIDC certificate identity is
.../release.yml@refs/heads/main
rather than the tag-ref identity
.../release.yml@refs/tags/v1.0.0
that tag-pushed releases produce.
Make verify_release_cosign try both identities so verification works
for both normal tag-triggered releases and workflow_dispatch back-fills.
If either matches, verification passes.
2026-08-29 10:01:58 +00:00
風雪 4f038eba42 feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
feat(release): cosign keyless signing of SHA256SUMS
Closes M-10 (supply-chain authenticity). Replaces the TODO comment with
a working cosign keyless OIDC signing flow.
Workflow (release.yml):
- Add id-token: write permission (required for GitHub OIDC token).
- Add sigstore/cosign-installer@v3 step.
- After SHA256SUMS is generated, cosign sign-blob --yes produces
SHA256SUMS.pem (certificate) and SHA256SUMS.sig (signature).
These are uploaded alongside the existing release assets.
- No long-lived secret is stored on GitHub; the signing identity is
the ephemeral GitHub Actions OIDC token bound to this workflow + tag.
- Release notes body updated with the cosign verify-blob command so
users know how to verify authenticity end-to-end.
Installers (install.sh / update.sh):
- Add verify_release_cosign() function. OPT-IN via
THREE_M_UI_VERIFY_COSIGN=1 (off by default to keep the curl|bash UX
simple — SHA256SUMS integrity is already fail-closed).
- When enabled AND cosign is installed, downloads SHA256SUMS.pem +
SHA256SUMS.sig and runs cosign verify-blob against the identity
'https://github.com/<repo>/.github/workflows/release.yml@refs/tags/<tag>'.
- If cosign is missing or the release predates signing, prints a
warning and continues (does not hard-fail).
- If verification runs and FAILS, aborts (fail-closed, bypassable via
THREE_M_UI_INSECURE=1).
Threat model:
- Before: SHA256SUMS provided integrity but not authenticity — an
attacker who could modify the release assets could also modify
SHA256SUMS, defeating the checksum.
- After: cosign signature ties SHA256SUMS to the GitHub Actions OIDC
identity, so a modified sums file fails signature verification even
if the attacker controls the release CDN.
Verification command (for end users):
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/kazeyukiro/3m-ui/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
SHA256SUMS
2026-08-29 09:58:00 +00:00
風雪 39608cd0fb fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
fix(infra): fail-closed checksums, sandbox hardening, non-root Docker
Critical (supply chain):
- C-3: Mihomo binary download had no checksum. Add verify_mihomo_sha256()
downloading MetaCubeX SHA256SUMS and verifying the .gz BEFORE
decompression. Mismatch is fatal.
- C-4: verify_release_sha256 soft-failed (return 0) when SHA256SUMS
was missing or asset unlisted. Now fail-closed: err exit unless the
operator sets THREE_M_UI_INSECURE=1 (prints a loud WARNING). sha256
tooling is now a hard dependency.
- C-5: Downloaded binaries were executed as root (--version / -v) for
smoke-testing. Remove every invocation; rely on checksum + 'file'
ELF check instead.
- H-1: Drop raw.githubusercontent.com fallback for helper scripts.
Missing release asset now hard-fails (supply-chain posture).
Sandbox:
- H-2: systemd unit gains UMask=0077, ProtectSystem=strict,
ProtectHome, PrivateTmp, PrivateDevices, ProtectKernelTunables/
Modules/Logs, ProtectControlGroups, ReadWritePaths,
RestrictAddressFamilies, RestrictNamespaces, RestrictRealtime,
RestrictSUIDSGID, LockPersonality, SystemCallArchitectures=native,
SystemCallFilter=@system-service ~@privileged @resources.
CapabilityBoundingSet now includes CAP_NET_ADMIN for Mihomo TUN.
- C-4(Docker): Dockerfile creates non-root user 3m-ui (UID 10001),
pre-creates & chowns volumes, sets USER 10001:10001.
- M-3: docker-compose ports 8080:8080 -> 127.0.0.1:8080:8080
(loopback-only by default; operator uses reverse proxy).
- M-9: docker-compose gains security notes (reverse-proxy guidance,
admin/admin + forced first-login change, THREE_M_UI_ADMIN_PASSWORD
example, UID 10001 volume ownership).
- M-10: release.yml gains a TODO comment for cosign keyless signing.
- LOW: AGENTS.md Go 1.24+ -> Go 1.25+; install.sh final message now
'Initial administrator: admin / admin. You MUST change the password
on first login.'
2026-08-29 09:39:25 +00:00
風雪 bb875405b3 fix(scripts): update.sh match SHA256SUMS with or without ./ prefix 2026-08-28 23:46:47 +08:00
風雪 4585f304a6 fix(scripts): checksum verify, versioned update, backup retention, safer traps 2026-08-25 00:02:36 +08:00
風雪 4d6241740d feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
feat: complete panel ACME SSL, sub HTML templates, Telegram alerts wiring
Wire acme/subpage into app and router, native v2ray/base64 subscription,
Delete depleted UI, Settings panels for SSL and subscription theme,
Telegram traffic/expiry thresholds. Docker + static multi-arch scripts.
2026-08-19 14:15:52 +00:00
風雪 40ab85ee74 feat(scripts): update.sh multi-arch static download + README docs 2026-08-19 21:03:43 +08:00
風雪 43a1d4767f fix updater repository owner 2026-08-17 22:56:33 +08:00
dzx941 5b4cb63a73 fix: align listener generation with mihomo official spec
- Port now supports mihomo ports format (single/range/list)
- Add explicit RoutingMark field support
- Auto-propagate UDP flag for vmess/vless/trojan/anytls/trusttunnel
- Update validator to accept port ranges
- Sync visual config and client converter port types
- Rename module from dzx941 to kazeyukiro
- Fix missing ExpiresAt in JWTClaims
- Fix nodeDTO Port type to string
- Fix frontend tsconfig and add missing dependencies
2026-08-16 09:21:42 +08:00