From d0a3784be28f401445a2871941ffb5512dc3d503 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 16:34:06 +0200 Subject: [PATCH] only-office: bar every account but the converter from the state root (issue 1843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib (bind-mounted into the container at /var/lib/onlyoffice) was 0711 so nginx inside the container could reach App_Data/cache, and the module root above it stayed 0711 too, so any account on the machine could walk through the root into lib's 0755 directories and 0644 files to read converted documents. The container never passes through the root — only the converter process does, to read jwt.secret and broker-converter.secret — so the root is now 0710, owned by the converter account: only that account and root pass through it. lib stays 0711 (1837/AC3). --- modules/only-office/manifest_test.go | 52 ++++++++++++++++++++++++++++ modules/only-office/module.json | 3 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/modules/only-office/manifest_test.go b/modules/only-office/manifest_test.go index 88cbddfe..7d28056f 100644 --- a/modules/only-office/manifest_test.go +++ b/modules/only-office/manifest_test.go @@ -125,3 +125,55 @@ func TestLibDirectoryLetsNginxThrough(t *testing.T) { } t.Fatal("manifest declares no lib directory") } + +// 1843/AC1 (unit: manifest check) +// Given the catalogue, when only-office's manifest is read, then its state directory (the module +// root, bind-mounted nowhere, so the container never passes through it) is mode 0710 and belongs +// to the converter account — the only host process that reaches through the root to read +// ${dir:state}/jwt.secret and broker-converter.secret — so no other account on the machine can +// pass through it to the converted documents under lib (which stays 0711, 1837/AC3). +func TestStateDirectoryBarsOtherAccounts(t *testing.T) { + raw, err := os.ReadFile("module.json") + if err != nil { + t.Fatalf("reading module.json: %v", err) + } + var m struct { + Resources []struct { + ID string `json:"id"` + Type string `json:"type"` + Mode string `json:"mode"` + Owner string `json:"owner"` + Name string `json:"name"` + } `json:"resources"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatalf("parsing module.json: %v", err) + } + + var converterAccount string + for _, r := range m.Resources { + if r.Type == "user" { + converterAccount = r.Name + } + } + if converterAccount == "" { + t.Fatal("manifest declares no user resource for the converter account") + } + + var sawState bool + for _, r := range m.Resources { + if r.ID == "state" && r.Type == "directory" { + sawState = true + if r.Mode != "0710" { + t.Errorf("state directory mode = %q, want 0710", r.Mode) + } + if r.Owner != converterAccount { + t.Errorf("state directory owner = %q, want %q, the converter account (whose own group "+ + "the mode's group bit then names)", r.Owner, converterAccount) + } + } + } + if !sawState { + t.Fatal("manifest declares no state directory") + } +} diff --git a/modules/only-office/module.json b/modules/only-office/module.json index e31fefc0..35987242 100644 --- a/modules/only-office/module.json +++ b/modules/only-office/module.json @@ -144,7 +144,8 @@ { "id": "state", "type": "directory", - "mode": "0711", + "mode": "0710", + "owner": "only-office-converter", "place": ".", "unknown": "program" },