Harden security: SSRF guard on all outbound fetches, auth fixes
- Route image-validation, Matrix image-download, and wayback/archive.today fetches through safehttp so feed-controlled URLs can't reach loopback/ RFC1918/cloud-metadata IPs (incl. via redirects). - Cap feed body size with safehttp.LimitedBody (16 MiB) to prevent OOM. - Fail closed if matrix.pickle_key is unset/<16 chars; drop the hardcoded "pete_pickle_key" default that silently weakened E2EE-at-rest. - Gate !post behind a matrix.admins allowlist; empty = disabled (channel rooms are public, so empty must not mean anyone). - Reject /\host open-redirect bypass in post-login safeNext. - Allowlist http(s) schemes for the Matrix link href; escape JSON embedded in inline <script> (prefs/sources blobs).
This commit is contained in:
@@ -50,6 +50,10 @@ type MatrixConfig struct {
|
||||
DataDir string `toml:"data_dir"`
|
||||
AdminRoom string `toml:"admin_room"`
|
||||
Channels map[string]string `toml:"channels"`
|
||||
// Admins is the allowlist of Matrix user IDs permitted to run in-room
|
||||
// commands like !post. Empty means commands are disabled — the channel
|
||||
// rooms are public, so an empty list must NOT mean "anyone".
|
||||
Admins []string `toml:"admins"`
|
||||
}
|
||||
|
||||
type PostingConfig struct {
|
||||
@@ -130,6 +134,12 @@ func (c *Config) validate() error {
|
||||
if c.Matrix.Password == "" {
|
||||
return fmt.Errorf("matrix.password is required")
|
||||
}
|
||||
// pickle_key encrypts the E2EE crypto store (olm/megolm + cross-signing
|
||||
// keys) at rest. Never fall back to a hardcoded default: a key baked into
|
||||
// the source is no protection if crypto.db ever leaks.
|
||||
if len(c.Matrix.PickleKey) < 16 {
|
||||
return fmt.Errorf("matrix.pickle_key must be set to a strong secret (>=16 chars); it encrypts the E2EE crypto store at rest")
|
||||
}
|
||||
if len(c.Matrix.Channels) == 0 {
|
||||
return fmt.Errorf("matrix.channels must have at least one entry")
|
||||
}
|
||||
@@ -184,9 +194,6 @@ func (c *Config) applyDefaults() {
|
||||
if c.Matrix.DisplayName == "" {
|
||||
c.Matrix.DisplayName = "Pete"
|
||||
}
|
||||
if c.Matrix.PickleKey == "" {
|
||||
c.Matrix.PickleKey = "pete_pickle_key"
|
||||
}
|
||||
if c.Posting.MinIntervalSeconds == 0 {
|
||||
c.Posting.MinIntervalSeconds = 300
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ const validMatrix = `
|
||||
homeserver = "https://matrix.example.org"
|
||||
user_id = "@pete:example.org"
|
||||
password = "testpass"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!tech:example.org"
|
||||
politics = "!politics:example.org"
|
||||
@@ -57,6 +58,7 @@ func TestEnvVarExpansion(t *testing.T) {
|
||||
homeserver = "https://matrix.example.org"
|
||||
user_id = "@pete:example.org"
|
||||
password = "${TEST_PETE_PASS}"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!tech:example.org"
|
||||
|
||||
@@ -114,6 +116,7 @@ func TestValidationErrors(t *testing.T) {
|
||||
[matrix]
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
@@ -123,6 +126,7 @@ db_path = "/tmp/t.db"
|
||||
[matrix]
|
||||
homeserver = "https://h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
@@ -142,6 +146,7 @@ db_path = "/tmp/t.db"
|
||||
homeserver = "https://h"
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[storage]
|
||||
db_path = "/tmp/t.db"
|
||||
`},
|
||||
@@ -150,6 +155,7 @@ db_path = "/tmp/t.db"
|
||||
homeserver = "https://h"
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
@@ -159,6 +165,7 @@ tech = "!t:e"
|
||||
homeserver = "https://h"
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
@@ -176,6 +183,7 @@ enabled = true
|
||||
homeserver = "https://h"
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
@@ -192,6 +200,7 @@ enabled = true
|
||||
homeserver = "https://h"
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
@@ -225,6 +234,7 @@ func TestDisabledSourceSkipsDirectRouteCheck(t *testing.T) {
|
||||
homeserver = "https://h"
|
||||
user_id = "@p:h"
|
||||
password = "pw"
|
||||
pickle_key = "test_pickle_key_1234"
|
||||
[matrix.channels]
|
||||
tech = "!t:e"
|
||||
[storage]
|
||||
|
||||
Reference in New Issue
Block a user