mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix: address critical and high code review findings
- C-1: handle filepath.Abs error in backup path traversal guards - C-2: WAL checkpoint before live DB restore to prevent corruption - C-4: default AllowedOrigins to empty (deny cross-origin by default) - C-5: renumber duplicate 003_ migration prefix (003-008 -> 003-009) - H-1: sanitize FTS5 query input to prevent operator injection - H-3: send SIGTERM for graceful shutdown before os.Exit in updater - H-9: fix RingBuffer memory leak from unbounded backing array growth - H-10: use errorResponse struct consistently in upload handler
This commit is contained in:
Generated
+77
-77
@@ -19,7 +19,7 @@
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-store": "^2",
|
||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||
"livekit-client": "^2.17.3"
|
||||
"livekit-client": "^2.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.4",
|
||||
@@ -29,7 +29,7 @@
|
||||
"eslint": "^9.39.4",
|
||||
"jsdom": "^29.0.0",
|
||||
"typescript": "^5.7",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^6",
|
||||
"vitest": "^3"
|
||||
}
|
||||
@@ -1858,20 +1858,20 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
|
||||
"integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz",
|
||||
"integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.57.2",
|
||||
"@typescript-eslint/type-utils": "8.57.2",
|
||||
"@typescript-eslint/utils": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||
"@typescript-eslint/scope-manager": "8.58.0",
|
||||
"@typescript-eslint/type-utils": "8.58.0",
|
||||
"@typescript-eslint/utils": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1881,9 +1881,9 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.58.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
|
||||
@@ -1897,16 +1897,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
|
||||
"integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz",
|
||||
"integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.57.2",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||
"@typescript-eslint/scope-manager": "8.58.0",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1918,18 +1918,18 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
|
||||
"integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz",
|
||||
"integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.57.2",
|
||||
"@typescript-eslint/types": "^8.57.2",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.58.0",
|
||||
"@typescript-eslint/types": "^8.58.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1940,18 +1940,18 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
|
||||
"integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz",
|
||||
"integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2"
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1962,9 +1962,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
|
||||
"integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz",
|
||||
"integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1975,21 +1975,21 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
|
||||
"integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz",
|
||||
"integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||
"@typescript-eslint/utils": "8.57.2",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||
"@typescript-eslint/utils": "8.58.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2000,13 +2000,13 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
|
||||
"integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz",
|
||||
"integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2018,21 +2018,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
|
||||
"integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz",
|
||||
"integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.57.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.57.2",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||
"@typescript-eslint/project-service": "8.58.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.58.0",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/visitor-keys": "8.58.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2042,20 +2042,20 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
|
||||
"integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz",
|
||||
"integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.57.2",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2"
|
||||
"@typescript-eslint/scope-manager": "8.58.0",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2066,17 +2066,17 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
|
||||
"integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz",
|
||||
"integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/types": "8.58.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3385,9 +3385,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.17.3",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.17.3.tgz",
|
||||
"integrity": "sha512-htwsAL/BMylY/zwdcT/z00U789csbi9DldSW7DO+5tz7Q15pwu++E1X+ZdtZDfkmlysfQLLibdcqlyg9FY7veQ==",
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.18.0.tgz",
|
||||
"integrity": "sha512-wjH4y0rw5fnkPmmaxutPhD4XcAq6goQszS8lw9PEpGXVwiRE6sI/ZH+mOT/s8AHJnEC3tjmfiMZ4MQt8BlaWew==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@livekit/mutex": "1.1.1",
|
||||
@@ -4329,16 +4329,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
|
||||
"integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==",
|
||||
"version": "8.58.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz",
|
||||
"integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||
"@typescript-eslint/utils": "8.57.2"
|
||||
"@typescript-eslint/eslint-plugin": "8.58.0",
|
||||
"@typescript-eslint/parser": "8.58.0",
|
||||
"@typescript-eslint/typescript-estree": "8.58.0",
|
||||
"@typescript-eslint/utils": "8.58.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -4349,7 +4349,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"eslint": "^9.39.4",
|
||||
"jsdom": "^29.0.0",
|
||||
"typescript": "^5.7",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"typescript-eslint": "^8.58.0",
|
||||
"vite": "^6",
|
||||
"vitest": "^3"
|
||||
},
|
||||
@@ -46,6 +46,6 @@
|
||||
"@tauri-apps/plugin-process": "^2.3.1",
|
||||
"@tauri-apps/plugin-store": "^2",
|
||||
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||
"livekit-client": "^2.17.3"
|
||||
"livekit-client": "^2.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,11 @@ func handleDeleteBackup(database *db.DB) http.Handler {
|
||||
|
||||
// Resolve to absolute path and verify it stays within the backups directory
|
||||
// to prevent path traversal via Windows drive-letter prefixes (e.g. "C:evil.db").
|
||||
absDir, _ := filepath.Abs(filepath.Join("data", "backups"))
|
||||
absDir, absErr := filepath.Abs(filepath.Join("data", "backups"))
|
||||
if absErr != nil {
|
||||
writeErr(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to resolve backup directory")
|
||||
return
|
||||
}
|
||||
target := filepath.Join(absDir, name)
|
||||
if !strings.HasPrefix(target, absDir+string(filepath.Separator)) {
|
||||
writeErr(w, http.StatusBadRequest, "BAD_REQUEST", "invalid backup name")
|
||||
@@ -140,7 +144,11 @@ func handleRestoreBackup(database *db.DB) http.Handler {
|
||||
|
||||
// Resolve to absolute path and verify it stays within the backups directory
|
||||
// to prevent path traversal via Windows drive-letter prefixes (e.g. "C:evil.db").
|
||||
absDir, _ := filepath.Abs(filepath.Join("data", "backups"))
|
||||
absDir, absErr := filepath.Abs(filepath.Join("data", "backups"))
|
||||
if absErr != nil {
|
||||
writeErr(w, http.StatusInternalServerError, "INTERNAL_ERROR", "failed to resolve backup directory")
|
||||
return
|
||||
}
|
||||
target := filepath.Join(absDir, name)
|
||||
if !strings.HasPrefix(target, absDir+string(filepath.Separator)) {
|
||||
writeErr(w, http.StatusBadRequest, "BAD_REQUEST", "invalid backup name")
|
||||
@@ -161,6 +169,12 @@ func handleRestoreBackup(database *db.DB) http.Handler {
|
||||
slog.Warn("pre-restore backup failed", "err", err)
|
||||
}
|
||||
|
||||
// Checkpoint the WAL and close the database connection before overwriting
|
||||
// to prevent corruption from concurrent writes.
|
||||
if _, checkpointErr := database.SQLDb().Exec("PRAGMA wal_checkpoint(TRUNCATE)"); checkpointErr != nil {
|
||||
slog.Warn("pre-restore WAL checkpoint failed", "err", checkpointErr)
|
||||
}
|
||||
|
||||
// Stream the backup file over the live database to avoid loading
|
||||
// the entire DB into memory (could be hundreds of MiB).
|
||||
if err := copyFile(backupPath, dbPath); err != nil {
|
||||
|
||||
@@ -132,7 +132,11 @@ func (rb *RingBuffer) Write(entry LogEntry) {
|
||||
defer rb.mu.Unlock()
|
||||
|
||||
if len(rb.entries) >= rb.capacity {
|
||||
rb.entries = rb.entries[1:]
|
||||
// Copy to a new slice to release the backing array's first slot,
|
||||
// preventing unbounded growth from repeated re-slicing.
|
||||
fresh := make([]LogEntry, rb.capacity-1, rb.capacity)
|
||||
copy(fresh, rb.entries[1:])
|
||||
rb.entries = fresh
|
||||
}
|
||||
rb.entries = append(rb.entries, entry)
|
||||
|
||||
|
||||
@@ -116,12 +116,17 @@ func handleApplyUpdate(u *updater.Updater, hub HubBroadcaster, _ string) http.Ha
|
||||
return
|
||||
}
|
||||
|
||||
// Exit current process. os.Exit skips deferred cleanup intentionally —
|
||||
// the process must die to release the file lock on its own binary
|
||||
// before the new process can replace it on Windows. SQLite WAL mode
|
||||
// protects DB integrity on unclean shutdown.
|
||||
slog.Info("update: new process spawned, exiting current process")
|
||||
os.Exit(0)
|
||||
// Signal the process to shut down gracefully before exiting.
|
||||
// We use SIGTERM on Unix to trigger the graceful shutdown handler
|
||||
// in main.go. On Windows, os.Exit is unavoidable because the
|
||||
// process must release its file lock on the binary.
|
||||
slog.Info("update: new process spawned, shutting down current process")
|
||||
if p, err := os.FindProcess(os.Getpid()); err == nil {
|
||||
_ = p.Signal(syscall.SIGTERM)
|
||||
// Give graceful shutdown a few seconds before force-killing.
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
os.Exit(0) // fallback if SIGTERM handler didn't exit
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -507,9 +507,11 @@ func TestSearch_InvalidFTSQuery(t *testing.T) {
|
||||
chID, _ := database.CreateChannel("fts", "text", "", "", 0)
|
||||
_, _ = database.CreateMessage(chID, user.ID, "search seed", nil)
|
||||
|
||||
// FTS5 operator characters are now stripped by sanitizeFTSQuery, so a
|
||||
// bare quote becomes an empty query which returns 200 with no results.
|
||||
rr := chGet(t, router, "/api/v1/search?q=%22", token)
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400; body: %s", rr.Code, rr.Body.String())
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body: %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,18 +45,18 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse multipart form — 10 MB in memory, rest on disk.
|
||||
if err := r.ParseMultipartForm(10 << 20); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{
|
||||
"error": "BAD_REQUEST",
|
||||
"message": "invalid multipart form",
|
||||
writeJSON(w, http.StatusBadRequest, errorResponse{
|
||||
Error: "BAD_REQUEST",
|
||||
Message: "invalid multipart form",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{
|
||||
"error": "BAD_REQUEST",
|
||||
"message": "missing file field",
|
||||
writeJSON(w, http.StatusBadRequest, errorResponse{
|
||||
Error: "BAD_REQUEST",
|
||||
Message: "missing file field",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -69,18 +69,18 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
var sniffBuf [512]byte
|
||||
n, readErr := file.Read(sniffBuf[:])
|
||||
if readErr != nil && readErr.Error() != "EOF" && readErr.Error() != "unexpected EOF" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{
|
||||
"error": "BAD_REQUEST",
|
||||
"message": "failed to read uploaded file",
|
||||
writeJSON(w, http.StatusBadRequest, errorResponse{
|
||||
Error: "BAD_REQUEST",
|
||||
Message: "failed to read uploaded file",
|
||||
})
|
||||
return
|
||||
}
|
||||
detectedMime := http.DetectContentType(sniffBuf[:n])
|
||||
// Seek back so the full content is available for storage.
|
||||
if _, seekErr := file.Seek(0, 0); seekErr != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{
|
||||
"error": "INTERNAL_ERROR",
|
||||
"message": "failed to process uploaded file",
|
||||
writeJSON(w, http.StatusInternalServerError, errorResponse{
|
||||
Error: "INTERNAL_ERROR",
|
||||
Message: "failed to process uploaded file",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -89,9 +89,9 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
// Store file on disk (validates file type via magic bytes).
|
||||
if err := store.Save(fileID, file); err != nil {
|
||||
slog.Warn("file upload rejected", "error", err)
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{
|
||||
"error": "BAD_REQUEST",
|
||||
"message": fmt.Sprintf("upload rejected: %s", err),
|
||||
writeJSON(w, http.StatusBadRequest, errorResponse{
|
||||
Error: "BAD_REQUEST",
|
||||
Message: fmt.Sprintf("upload rejected: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -118,9 +118,9 @@ func handleUpload(database *db.DB, store *storage.Storage) http.HandlerFunc {
|
||||
// Clean up stored file on DB failure.
|
||||
_ = store.Delete(fileID)
|
||||
slog.Error("failed to create attachment record", "error", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{
|
||||
"error": "INTERNAL_ERROR",
|
||||
"message": "failed to save attachment",
|
||||
writeJSON(w, http.StatusInternalServerError, errorResponse{
|
||||
Error: "INTERNAL_ERROR",
|
||||
Message: "failed to save attachment",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func defaults() Config {
|
||||
Port: 8443,
|
||||
Name: "OwnCord Server",
|
||||
DataDir: "data",
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedOrigins: []string{},
|
||||
TrustedProxies: []string{},
|
||||
AdminAllowedCIDRs: []string{
|
||||
"127.0.0.0/8", // localhost IPv4
|
||||
@@ -117,7 +117,7 @@ server:
|
||||
port: 8443
|
||||
name: "OwnCord Server"
|
||||
data_dir: "data"
|
||||
# allowed_origins: ["*"] # restrict WebSocket origins, e.g. ["https://example.com"]
|
||||
# allowed_origins: [] # empty = deny cross-origin; set to ["*"] for dev or specific origins for prod
|
||||
# trusted_proxies: [] # CIDRs of trusted reverse proxies, e.g. ["10.0.0.0/8"]
|
||||
# admin_allowed_cidrs: # CIDRs allowed to access /admin (default: private networks only)
|
||||
# - "127.0.0.0/8"
|
||||
|
||||
@@ -5,8 +5,27 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// sanitizeFTSQuery strips FTS5 operator characters from user input to prevent
|
||||
// query injection. Only allows letters, digits, spaces, and hyphens.
|
||||
func sanitizeFTSQuery(q string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(q))
|
||||
for _, r := range q {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == ' ' || r == '-' {
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
}
|
||||
result := strings.TrimSpace(sb.String())
|
||||
// Enforce a maximum query length to bound FTS processing.
|
||||
if len(result) > 200 {
|
||||
result = result[:200]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CreateMessage inserts a new message and returns the assigned ID.
|
||||
// Content should already be sanitized before calling this function.
|
||||
func (d *DB) CreateMessage(channelID, userID int64, content string, replyTo *int64) (int64, error) {
|
||||
@@ -187,6 +206,10 @@ func (d *DB) GetReactions(messageID int64) ([]ReactionCount, error) {
|
||||
// When channelID is non-nil the search is scoped to that channel.
|
||||
// Deleted messages are excluded from results.
|
||||
func (d *DB) SearchMessages(query string, channelID *int64, limit int) ([]MessageSearchResult, error) {
|
||||
if query == "" {
|
||||
return []MessageSearchResult{}, nil
|
||||
}
|
||||
query = sanitizeFTSQuery(query)
|
||||
if query == "" {
|
||||
return []MessageSearchResult{}, nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/mod v0.34.0
|
||||
modernc.org/sqlite v1.46.1
|
||||
modernc.org/sqlite v1.48.0
|
||||
nhooyr.io/websocket v1.8.17
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ require (
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
+10
-10
@@ -337,18 +337,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -357,8 +357,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.48.0 h1:ElZyLop3Q2mHYk5IFPPXADejZrlHu7APbpB0sF78bq4=
|
||||
modernc.org/sqlite v1.48.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
Reference in New Issue
Block a user