Commit Graph
15 Commits
Author SHA1 Message Date
Z User 5b9674987f fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
fix(backup): restore mihomo-config.yaml from backup zip
Backup zip contains 3m-ui.db + mihomo-config.yaml + backup-meta.txt.
RestoreDatabase only extracted 3m-ui.db — mihomo-config.yaml was
silently dropped. After restoring a backup, the mihomo config file
on disk was stale (from before the backup was taken).
Fix:
- extractDBFromZip now returns both dbContent and mihomoCfg
- RestoreDatabase takes mihomoCfgPath parameter
- After restoring the DB, also writes mihomo-config.yaml if the
zip contained it and the path is configured
- Mihomo config write failure does NOT fail the whole restore
(DB is already restored; operator can manually restore config)
- api.go passes h.mihomoCfg to RestoreDatabase
2026-08-31 03:42:32 +00:00
Z User da5a0f67d1 fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
fix(audit-r1r2): visual config JSON inline, batch name check, monthly reset, credential sync, restore temp, access profile fallback
Round 1+2 exhaustive audit found 6 bugs. All fixed.
HIGH:
- VisualConfig ProxyEntry: Go JSON doesn't support yaml inline. Added
custom UnmarshalJSON/MarshalJSON to capture/emit flat fields.
- BatchCreate: name uniqueness only checked enabled listeners. Removed
filter to match UNIQUE index scope.
- Monthly reset: could fire multiple times per day if DB locked. Now
claims the day in a transaction BEFORE resetting.
- Batch/delete ops: silently ignored notifyCredentialsChanged errors.
Now logs them.
MEDIUM:
- Global AccessProfile: PublicPort/SNI/ClientFingerprint/ALPN loaded
but never used as fallbacks. Now applied when per-listener empty.
- RestoreDatabase: temp file not cleaned on failure. Added defer remove.
go build/vet/test: all PASS. gofmt: clean.
2026-08-30 15:14:36 +00:00
Z User 266b59bee1 fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
fix(backup): RestoreDatabase now handles zip format (the actual backup format)
ROOT CAUSE: ExportBackup produces a .zip file (3m-ui-backup-*.zip
containing 3m-ui.db + mihomo-config.yaml + backup-meta.txt). But
RestoreDatabase treated the uploaded file as a raw SQLite database —
it io.Copy'd the entire zip file into the DB path without extracting.
On restart, GORM/SQLite opened the zip file as a DB, found it wasn't
a valid SQLite format, and the panel failed to start.
This is why 'restoring backup and restarting 3m-ui' made the panel
unable to boot — the DB file was actually a zip archive.
Fix: RestoreDatabase now:
1. Reads the upload into memory (128 MiB limit enforced by API handler)
2. Checks the PK\x03\x04 zip magic bytes
3. If zip: extracts 3m-ui.db from the archive via zip.NewReader
4. If not zip: treats as raw SQLite DB (backward compatible)
5. Writes extracted DB content to the temp file + atomic rename
This fixes both scenarios:
- User downloads .zip backup → uploads it for restore → now works
- User has a raw .db file → uploads it for restore → still works
2026-08-30 11:56:37 +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
風雪 c852f0266a fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
fix(backend): path traversal, mihomo secret, template injection, acme path
- H-3: subpage loadTemplate accepted arbitrary theme_dir, reachable
from the unauthenticated /api/v1/client/sub/:token?html=1 endpoint.
Add sanitizeThemeDir() restricting to /var/lib/3m-ui/themes,
/etc/3m-ui/themes, /usr/local/share/3m-ui/themes. Validated on both
load and save.
- M-1: Mihomo external-controller shipped with an empty secret. Even
bound to loopback, any co-located unprivileged process could drive
the REST API. Generate a process-scoped random secret via crypto/rand
(ControllerSecret()); traffic collector already reads it from the
generated config.
- H-4: ACMECommand / WARPTemplate / ReverseProxyTemplate used
fmt.Sprintf with unescaped user input (shell / YAML / nginx injection
if admin account is hijacked). Add isValidDomain() and shellQuote().
ACMECommand and WARPTemplate now return (string, error). WARP uses
yaml.Marshal for structured output. api.go handlers updated.
- LOW-12: ACME cert_file/key_file paths now restricted to an allowlist
(/etc/ssl/, /etc/letsencrypt/, /etc/3m-ui/, /var/lib/3m-ui/, etc.).
2026-08-29 09:38:43 +00:00
風雪 475c1fc172 style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
style: gofmt for CI
2026-08-21 16:40:11 +00:00
風雪 885a68e57f chore: remove internal parity wording from comments and UI 2026-08-20 15:40:26 +00:00
風雪 31e12d56db fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
fix: dashboard CPU/memory/disk metrics units and sampling
- Memory and disk used/total now returned as bytes (frontend formatBytes)
- Clamp percents to 0–100; improve CPU sampling (200ms + fallback)
- Disk usage probes /, HOME, panel data dirs
- Dashboard: safer formatBytes and Progress clamping
2026-08-20 05:34:07 +00:00
風雪 479ef4e0fa feat: more parity — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more 3x-ui parity — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more 3x-ui parity — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more 3x-ui parity — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more 3x-ui parity — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
feat: more panel features — sing-box sub, login TG notify, WARP, search
- Native sing-box JSON subscription (?target=singbox) + UA auto-detect
- Enhanced ?format=info with multi-format links and blocked flag
- Telegram notify on panel login (settings toggle)
- Telegram /backup command; listener list ?q= / enabled filters
- System WARP WireGuard YAML template endpoint
- Frontend: Sing-box share link, login notify switch, listener search
2026-08-20 03:38:36 +00:00
風雪 af555c9dcc feat: parity — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: 3x-ui parity — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: 3x-ui parity — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: 3x-ui parity — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: 3x-ui parity — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition (3x-ui style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
feat: panel features — batch users, sub headers, telegram cmds, geofiles
- User list filters (?q/enabled/online/blocked) and POST /users/batch
- Subscription Profile-Title / Content-Disposition ( style)
- Telegram: /restart /deldepleted /search
- System: POST /system/geofiles/update (MetaCubeX geoip/geosite)
- Frontend: batch toolbar + geo update button + i18n
2026-08-20 01:24:43 +00:00
風雪 99a4cb6587 fix: cap database restore upload size 2026-08-18 09:39:22 +08:00
風雪 26bbce128b feat: system reverse-proxy/ACME template API handlers 2026-08-17 08:24:50 +08:00
風雪 d238ba470e feat: migrate PanelSetting/RemoteServer; ACME/nginx templates 2026-08-16 22:56:07 +08:00
風雪 ce9b718c2e feat: system backup/restore API and OpenAPI embed helper 2026-08-16 21:56:40 +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