fix(A.1 #2): parse tauri.conf.json properly in CSP regression guard
Review feedback: the original guard substring-searched the whole file after the first "csp" token, which (a) false-passes if any unrelated JSON value elsewhere in the config happens to contain a localhost URL and (b) false-fails if the CSP is ever re-serialised with escaped forward slashes. Switches to serde_json + a /app/security/csp pointer lookup, then splits the CSP on ';', finds the connect-src directive, tokenises its allow-list on whitespace, and requires an exact match for both http://127.0.0.1:* and ws://127.0.0.1:*. The error now also includes the current connect-src value so a developer who breaks it can see exactly what needs restoring.
This commit is contained in:
@@ -11,6 +11,11 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
# Used by the CSP regression guard in build.rs to parse tauri.conf.json
|
||||||
|
# and pull out app.security.csp rather than substring-matching the raw
|
||||||
|
# file. Keeps the guard robust against unrelated JSON values that
|
||||||
|
# happen to mention a localhost URL.
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Workspace crates
|
# Workspace crates
|
||||||
|
|||||||
@@ -21,23 +21,46 @@ fn main() {
|
|||||||
/// `fetch()` from the webview to the local LLM silently 404s with an
|
/// `fetch()` from the webview to the local LLM silently 404s with an
|
||||||
/// opaque scope error (Vibe #438 / #487). We keep the current permit
|
/// opaque scope error (Vibe #438 / #487). We keep the current permit
|
||||||
/// pinned at build time so a stray edit can't regress it unnoticed.
|
/// pinned at build time so a stray edit can't regress it unnoticed.
|
||||||
|
///
|
||||||
|
/// Parses `tauri.conf.json` properly and inspects the `connect-src`
|
||||||
|
/// directive of the CSP, rather than substring-searching the whole
|
||||||
|
/// file — the latter would both false-pass on unrelated values
|
||||||
|
/// containing a localhost URL and false-fail on JSON-escaped forward
|
||||||
|
/// slashes.
|
||||||
fn assert_localhost_llm_csp() {
|
fn assert_localhost_llm_csp() {
|
||||||
let conf_path = std::path::Path::new("tauri.conf.json");
|
let conf_path = std::path::Path::new("tauri.conf.json");
|
||||||
println!("cargo:rerun-if-changed=tauri.conf.json");
|
println!("cargo:rerun-if-changed=tauri.conf.json");
|
||||||
|
|
||||||
let raw = std::fs::read_to_string(conf_path)
|
let raw = std::fs::read_to_string(conf_path)
|
||||||
.expect("build.rs: failed to read tauri.conf.json for CSP regression guard");
|
.expect("build.rs: failed to read tauri.conf.json for CSP regression guard");
|
||||||
let csp_start = raw
|
let conf: serde_json::Value = serde_json::from_str(&raw)
|
||||||
.find("\"csp\"")
|
.expect("build.rs: tauri.conf.json is not valid JSON");
|
||||||
.expect("build.rs: tauri.conf.json missing \"csp\" field");
|
|
||||||
// Read the rest of the CSP string — we only need to substring-search.
|
let csp = conf
|
||||||
let csp_slice = &raw[csp_start..];
|
.pointer("/app/security/csp")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.expect("build.rs: tauri.conf.json missing app.security.csp string");
|
||||||
|
|
||||||
|
// Split the CSP into directives (`default-src 'self'; script-src ...`)
|
||||||
|
// and find connect-src. Everything after the directive name up to the
|
||||||
|
// next `;` is the allow-list.
|
||||||
|
let connect_src = csp
|
||||||
|
.split(';')
|
||||||
|
.map(str::trim)
|
||||||
|
.find_map(|directive| directive.strip_prefix("connect-src"))
|
||||||
|
.map(str::trim)
|
||||||
|
.expect(
|
||||||
|
"build.rs: tauri.conf.json CSP missing connect-src directive — \
|
||||||
|
local LLM fetches will be blocked (brief item #2)",
|
||||||
|
);
|
||||||
|
|
||||||
|
let tokens: Vec<&str> = connect_src.split_whitespace().collect();
|
||||||
for required in ["http://127.0.0.1:*", "ws://127.0.0.1:*"] {
|
for required in ["http://127.0.0.1:*", "ws://127.0.0.1:*"] {
|
||||||
assert!(
|
assert!(
|
||||||
csp_slice.contains(required),
|
tokens.iter().any(|t| *t == required),
|
||||||
"build.rs: tauri.conf.json CSP must permit {required} for local LLM \
|
"build.rs: tauri.conf.json CSP connect-src must permit {required} \
|
||||||
connectivity (brief item #2). Restore the connect-src entry before \
|
for local LLM connectivity (brief item #2). Current connect-src: \
|
||||||
building."
|
{connect_src:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user