Skip to content

Commit d7e42bd

Browse files
committed
Prevent non-local origins via CORS rule.
1 parent 8aae0de commit d7e42bd

File tree

2 files changed

+15
-23
lines changed

2 files changed

+15
-23
lines changed

crates/but-server/SECURITY.md

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,7 @@ The server uses an Axum middleware (`localhost_only_middleware`) that:
1313
3. Accepts the connection if it's from localhost
1414
4. Rejects the connection with HTTP 403 Forbidden if it's from any other address
1515

16-
## Testing
17-
18-
To verify the security middleware:
19-
20-
1. Start the server:
21-
```bash
22-
cargo run -p but-server
23-
```
24-
25-
2. Test from localhost (should succeed):
26-
```bash
27-
curl -i http://127.0.0.1:6978/
28-
```
29-
30-
3. Attempt to connect from a non-localhost address (should fail with 403):
31-
- This can only be tested if the server is bound to a non-localhost address
32-
- By default, the server binds to 127.0.0.1 which already prevents remote connections at the TCP level
33-
- The middleware provides defense-in-depth even if the bind address is changed
16+
When this passes, it additionally validates the origin header is set and comes from `http://localhost:`.
3417

3518
## Configuration
3619

@@ -46,3 +29,7 @@ Rejected connections are logged with a warning:
4629
```
4730
Rejected non-localhost connection from: <ip_address>
4831
```
32+
33+
# Considerations for Electron
34+
35+
Should the `but-server` be used in Electron, we will make sure that it's completely locked down and only usable by the Electron process it's bundled with.

crates/but-server/src/lib.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
2222
use serde_json::json;
2323
use std::net::SocketAddr;
2424
use tokio::sync::Mutex;
25-
use tower_http::cors::{Any, CorsLayer};
25+
use tower_http::cors::{self, CorsLayer};
2626

2727
mod projects;
2828
use crate::projects::ActiveProjects;
@@ -71,9 +71,14 @@ async fn localhost_only_middleware(
7171

7272
pub async fn run() {
7373
let cors = CorsLayer::new()
74-
.allow_methods(Any)
75-
.allow_origin(Any)
76-
.allow_headers(Any);
74+
.allow_methods(cors::Any)
75+
.allow_origin(cors::AllowOrigin::predicate(|origin, _parts| {
76+
origin
77+
.as_bytes()
78+
.strip_prefix(b"http://localhost")
79+
.is_some_and(|rest| rest.first().is_none_or(|b| *b == b':'))
80+
}))
81+
.allow_headers(cors::Any);
7782

7883
let config_dir = but_path::app_config_dir().unwrap();
7984
let app_data_dir = but_path::app_data_dir().unwrap();
@@ -124,7 +129,7 @@ pub async fn run() {
124129
.layer(cors)
125130
// Middleware to ensure only localhost connections are accepted.
126131
// Note: In Axum, layers are applied in reverse order, so this middleware
127-
// runs BEFORE CORS processing, ensuring security checks happen first.
132+
// runs BEFORE CORS processing, ensuring this security checks happen first.
128133
.layer(axum::middleware::from_fn(localhost_only_middleware))
129134
.with_state(state);
130135

0 commit comments

Comments
 (0)