From d0faee4eeb7f6bad504d30e069a3cd6a14b2acad Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 18 May 2026 23:34:01 +0800 Subject: [PATCH] feat: improve frontend permission handling (#12760) * feat: change isProductPro logic (#12712) * fix: tighten frontend RBAC permission guards (#12717) * fix: tighten frontend RBAC permission guards * feat: add permission directive coverage * refactor frontend global store usage * fix: harden file access and fallback handling * feat: improve frontend permission handling --------- Co-authored-by: CityFun <31820853+zhengkunwang223@users.noreply.github.com> --- agent/app/api/v2/file.go | 12 ++++++ agent/app/service/file.go | 11 +++++ agent/app/service/file_share.go | 4 ++ agent/i18n/lang/en.yaml | 1 + agent/i18n/lang/es-ES.yaml | 1 + agent/i18n/lang/ja.yaml | 1 + agent/i18n/lang/ko.yaml | 1 + agent/i18n/lang/ms.yaml | 1 + agent/i18n/lang/pt-BR.yaml | 1 + agent/i18n/lang/ru.yaml | 1 + agent/i18n/lang/tr.yaml | 1 + agent/i18n/lang/zh-Hant.yaml | 1 + agent/i18n/lang/zh.yaml | 1 + agent/utils/files/path_filter.go | 26 +++++++++++ .../src/components/router-button/index.vue | 24 +++++++++-- .../composables/useMenuManagePermission.ts | 28 +++++++++--- frontend/src/global/use-theme.ts | 33 ++++++++------ frontend/src/lang/modules/en.ts | 6 ++- frontend/src/lang/modules/es-es.ts | 5 ++- frontend/src/lang/modules/ja.ts | 5 ++- frontend/src/lang/modules/ko.ts | 5 ++- frontend/src/lang/modules/ms.ts | 6 ++- frontend/src/lang/modules/pt-br.ts | 5 ++- frontend/src/lang/modules/ru.ts | 5 ++- frontend/src/lang/modules/tr.ts | 5 ++- frontend/src/lang/modules/zh-Hant.ts | 4 +- frontend/src/lang/modules/zh.ts | 4 +- .../src/layout/components/Sidebar/index.vue | 9 +--- frontend/src/routers/index.ts | 12 ++++-- frontend/src/typings/global.d.ts | 1 + frontend/src/utils/permission.ts | 13 +++++- frontend/src/utils/rbac.ts | 43 ++++++++++++------- frontend/src/utils/xpack.ts | 4 +- .../config/tabs/channels/openclaw/discord.vue | 1 - .../tabs/channels/openclaw/telegram.vue | 1 - frontend/src/views/setting/panel/index.vue | 13 ------ frontend/src/views/terminal/index.vue | 1 + .../php/extension-management/index.vue | 2 + .../runtime/php/extension-template/index.vue | 1 + 39 files changed, 220 insertions(+), 79 deletions(-) diff --git a/agent/app/api/v2/file.go b/agent/app/api/v2/file.go index 6d4173197..22b892c2f 100644 --- a/agent/app/api/v2/file.go +++ b/agent/app/api/v2/file.go @@ -640,6 +640,10 @@ func (b *BaseApi) MoveFile(c *gin.Context) { // @Router /files/download [get] func (b *BaseApi) Download(c *gin.Context) { filePath := c.Query("path") + if files.ShouldDenySensitiveFileRead(filePath) { + helper.InternalServer(c, buserr.New("ErrSensitiveFileRead")) + return + } file, err := os.Open(filePath) if err != nil { helper.InternalServer(c, err) @@ -675,6 +679,10 @@ func (b *BaseApi) DownloadChunkFiles(c *gin.Context) { helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPathNotFound", nil) return } + if files.ShouldDenySensitiveFileRead(req.Path) { + helper.InternalServer(c, buserr.New("ErrSensitiveFileRead")) + return + } filePath := req.Path fstFile, err := fileOp.OpenFile(filePath) if err != nil { @@ -1335,6 +1343,10 @@ func (b *BaseApi) DownloadFileShare(c *gin.Context) { helper.InternalServer(c, err) return } + if files.ShouldDenySensitiveFileRead(filePath) { + helper.InternalServer(c, buserr.New("ErrSensitiveFileRead")) + return + } file, err := os.Open(filePath) if err != nil { helper.InternalServer(c, err) diff --git a/agent/app/service/file.go b/agent/app/service/file.go index d8dc8b2e7..d376b54e4 100644 --- a/agent/app/service/file.go +++ b/agent/app/service/file.go @@ -669,6 +669,9 @@ func applyDecompressOwnership(srcPath, dstPath string) error { } func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo, error) { + if files.ShouldDenySensitiveFileRead(op.Path) { + return response.FileInfo{}, buserr.New("ErrSensitiveFileRead") + } info, err := files.NewFileInfo(files.FileOption{ Path: op.Path, Expand: true, @@ -705,6 +708,9 @@ func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo, } func (f *FileService) GetPreviewContent(op request.FileContentReq) (response.FileInfo, error) { + if files.ShouldDenySensitiveFileRead(op.Path) { + return response.FileInfo{}, buserr.New("ErrSensitiveFileRead") + } info, err := files.NewFileInfo(files.FileOption{ Path: op.Path, Expand: false, @@ -945,6 +951,11 @@ func buildHistoryMoveTargetPath(dst, name, sourcePath string, sourceCount int) s } func (f *FileService) FileDownload(d request.FileDownload) (string, error) { + for _, p := range d.Paths { + if files.ShouldDenySensitiveFileRead(p) { + return "", buserr.New("ErrSensitiveFileRead") + } + } filePath := d.Paths[0] if d.Compress { tempPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano())) diff --git a/agent/app/service/file_share.go b/agent/app/service/file_share.go index 9718e712c..8b0ed91ad 100644 --- a/agent/app/service/file_share.go +++ b/agent/app/service/file_share.go @@ -20,6 +20,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/repo" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/files" "gorm.io/gorm" ) @@ -131,6 +132,9 @@ func (s *FileShareService) Create(req request.FileShareCreate) (*response.FileSh if path == "" || strings.Contains(path, "..") { return nil, buserr.New("ErrFileSharePath") } + if files.ShouldDenySensitiveFileRead(path) { + return nil, buserr.New("ErrSensitiveFileRead") + } info, err := os.Stat(path) if err != nil || info.IsDir() { return nil, buserr.New("ErrFileSharePath") diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index 045bfc2ef..910e97a2b 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -139,6 +139,7 @@ ExportDate: "Time" #file ErrFileCanNotRead: 'File preview is not supported' +ErrSensitiveFileRead: 'Reading sensitive files is not allowed' ErrFileToLarge: 'File is larger than 10 MB' ErrPathNotFound: 'Directory does not exist' ErrMovePathFailed: 'Target path cannot include source path' diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml index 5f674ac5f..c93803e5b 100644 --- a/agent/i18n/lang/es-ES.yaml +++ b/agent/i18n/lang/es-ES.yaml @@ -134,6 +134,7 @@ ExportUser: 'Usuario' ExportStatus: 'Estado de inicio de sesión' ExportDate: 'Fecha y hora' ErrFileCanNotRead: 'Este archivo no soporta vista previa' +ErrSensitiveFileRead: 'No se permite leer archivos sensibles' ErrFileToLarge: 'El archivo es mayor a 10M y no puede abrirse' ErrPathNotFound: 'El directorio no existe' ErrMovePathFailed: 'La ruta de destino no puede contener la ruta original' diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml index 0e4b46d2f..8fffc9c46 100644 --- a/agent/i18n/lang/ja.yaml +++ b/agent/i18n/lang/ja.yaml @@ -134,6 +134,7 @@ ExportUser: 'ユーザー' ExportStatus: 'ログイン状態' ExportDate: '時間' ErrFileCanNotRead: 'このファイルはプレビューをサポートしていません' +ErrSensitiveFileRead: '機密ファイルの読み取りは許可されていません' ErrFileToLarge: 'ファイルは 10M より大きいため開けません' ErrPathNotFound: 'ディレクトリが存在しません' ErrMovePathFailed: 'ターゲット パスに元のパスを含めることはできません' diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml index 0c030a689..fd76983f7 100644 --- a/agent/i18n/lang/ko.yaml +++ b/agent/i18n/lang/ko.yaml @@ -134,6 +134,7 @@ ExportUser: '사용자' ExportStatus: '로그인 상태' ExportDate: '시간' ErrFileCanNotRead: '이 파일은 미리보기를 지원하지 않습니다' +ErrSensitiveFileRead: '민감한 파일을 읽을 수 없습니다' ErrFileToLarge: '파일이 10M보다 커서 열 수 없습니다' ErrPathNotFound: '디렉토리가 존재하지 않습니다' ErrMovePathFailed: '대상 경로에는 원래 경로가 포함될 수 없습니다' diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml index d55bb9d20..3d6f02c58 100644 --- a/agent/i18n/lang/ms.yaml +++ b/agent/i18n/lang/ms.yaml @@ -134,6 +134,7 @@ ExportUser: 'Pengguna' ExportStatus: 'Status Log Masuk' ExportDate: 'Masa' ErrFileCanNotRead: 'Fail ini tidak menyokong pratonton' +ErrSensitiveFileRead: 'Membaca fail sensitif tidak dibenarkan' ErrFileToLarge: 'Fail lebih besar daripada 10M dan tidak boleh dibuka' ErrPathNotFound: 'Direktori tidak wujud' ErrMovePathFailed: 'Laluan sasaran tidak boleh mengandungi laluan asal' diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml index 632854c02..3513d6771 100644 --- a/agent/i18n/lang/pt-BR.yaml +++ b/agent/i18n/lang/pt-BR.yaml @@ -134,6 +134,7 @@ ExportUser: 'Usuário' ExportStatus: 'Status de Login' ExportDate: 'Hora' ErrFileCanNotRead: 'Este arquivo não suporta visualização' +ErrSensitiveFileRead: 'A leitura de arquivos sensíveis não é permitida' ErrFileToLarge: 'O arquivo é maior que 10M e não pode ser aberto' ErrPathNotFound: 'O diretório não existe' ErrMovePathFailed: 'O caminho de destino não pode conter o caminho original' diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml index 76c8deac7..9eae4f9d2 100644 --- a/agent/i18n/lang/ru.yaml +++ b/agent/i18n/lang/ru.yaml @@ -134,6 +134,7 @@ ExportUser: 'Пользователь' ExportStatus: 'Статус входа' ExportDate: 'Время' ErrFileCanNotRead: 'Этот файл не поддерживает предварительный просмотр' +ErrSensitiveFileRead: 'Чтение конфиденциальных файлов запрещено' ErrFileToLarge: 'Файл больше 10 МБ и не может быть открыт' ErrPathNotFound: 'Каталог не существует' ErrMovePathFailed: 'Целевой путь не может содержать исходный путь' diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml index 98c8e4345..a1ee7a240 100644 --- a/agent/i18n/lang/tr.yaml +++ b/agent/i18n/lang/tr.yaml @@ -134,6 +134,7 @@ ExportUser: 'Kullanıcı' ExportStatus: 'Giriş Durumu' ExportDate: 'Zaman' ErrFileCanNotRead: 'Bu dosya önizlemeyi desteklemiyor' +ErrSensitiveFileRead: 'Hassas dosyaların okunmasına izin verilmez' ErrFileToLarge: 'Dosya 10M dan büyük ve açılamıyor' ErrPathNotFound: 'Dizin mevcut değil' ErrMovePathFailed: 'Hedef yol orijinal yolu içeremez' diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index 955af078c..4bdd39239 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -134,6 +134,7 @@ ExportUser: '使用者' ExportStatus: '登入狀態' ExportDate: '時間' ErrFileCanNotRead: '此檔案不支援預覽' +ErrSensitiveFileRead: '不允許讀取敏感檔案' ErrFileToLarge: '檔案超過10M,無法開啟' ErrPathNotFound: '目錄不存在' ErrMovePathFailed: '目標路徑不可包含原路徑!' diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index bbd3974f9..0b0c89777 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -139,6 +139,7 @@ ExportDate: "时间" #file ErrFileCanNotRead: "此文件不支持预览" +ErrSensitiveFileRead: "不允许读取敏感文件" ErrFileToLarge: "文件超过 10M,无法打开" ErrPathNotFound: "目录不存在" ErrMovePathFailed: "目标路径不能包含原路径!" diff --git a/agent/utils/files/path_filter.go b/agent/utils/files/path_filter.go index 39e3ef13e..b9f428522 100644 --- a/agent/utils/files/path_filter.go +++ b/agent/utils/files/path_filter.go @@ -17,5 +17,31 @@ func ShouldFilterSensitivePath(path string) bool { return true } } + return ShouldDenySensitiveFileRead(path) +} + +func ShouldDenySensitiveFileRead(path string) bool { + if isSensitiveSSHPath(path) { + return true + } + realPath, err := filepath.EvalSymlinks(path) + if err != nil { + return false + } + return isSensitiveSSHPath(realPath) +} + +func isSensitiveSSHPath(path string) bool { + cleanedPath := filepath.Clean(path) + parts := strings.Split(filepath.ToSlash(cleanedPath), "/") + for i, part := range parts { + if part != ".ssh" { + continue + } + if i == len(parts)-1 { + return true + } + return !strings.HasSuffix(parts[len(parts)-1], ".pub") + } return false } diff --git a/frontend/src/components/router-button/index.vue b/frontend/src/components/router-button/index.vue index 48ff83df0..3783fce7e 100644 --- a/frontend/src/components/router-button/index.vue +++ b/frontend/src/components/router-button/index.vue @@ -24,8 +24,9 @@