A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: return the correct host data for plugin secrets

Trezy ef6e23c2 effa3677

+79 -1
+6 -1
src/plugin/host/bindings.rs
··· 182 182 183 183 /// Host function: get a secret value by name 184 184 /// Returns a packed i64: (ptr << 32) | len, or 0 on error 185 + /// 186 + /// The response is JSON-encoded as `{"ok": "<value>"}` so plugins can 187 + /// deserialize it with the same `Response<String>` envelope they use for 188 + /// other host calls. 185 189 async fn host_get_secret_impl( 186 190 caller: &mut wasmtime::Caller<'_, PluginState>, 187 191 name_ptr: i32, ··· 197 201 None => return 0, 198 202 }; 199 203 200 - write_guest_response(caller, value.as_bytes()).await 204 + let response_bytes = serde_json::to_vec(&serde_json::json!({"ok": value})).unwrap_or_default(); 205 + write_guest_response(caller, &response_bytes).await 201 206 } 202 207 203 208 // ============================================================================
tests/fixtures/steam.wasm

This is a binary file and will not be displayed.

+73
tests/plugin_executor.rs
··· 228 228 229 229 assert!(matches!(result, Err(ExecutionError::PluginNotFound(_)))); 230 230 } 231 + 232 + /// Test that host_get_secret returns a JSON envelope that plugins can parse. 233 + /// Uses the real Steam plugin WASM which calls get_secret("API_KEY") in get_profile. 234 + /// This test verifies the fix for the JSON envelope mismatch where the host 235 + /// returned raw bytes but the plugin expected {"ok": "value"}. 236 + #[tokio::test] 237 + async fn test_steam_get_secret_json_envelope() { 238 + let wasm_path = "tests/fixtures/steam.wasm"; 239 + if !std::path::Path::new(wasm_path).exists() { 240 + eprintln!("Skipping test: {} not found", wasm_path); 241 + return; 242 + } 243 + 244 + let (executor, registry) = create_test_executor().await; 245 + let wasm_bytes = std::fs::read(wasm_path).expect("Failed to read steam.wasm"); 246 + 247 + let plugin = LoadedPlugin { 248 + info: PluginInfo { 249 + id: "steam".into(), 250 + name: "Steam".into(), 251 + version: "1.1.0".into(), 252 + api_version: "1".into(), 253 + icon_url: None, 254 + required_secrets: vec!["API_KEY".into()], 255 + auth_type: "openid".into(), 256 + config_schema: None, 257 + }, 258 + source: PluginSource::File { 259 + path: wasm_path.into(), 260 + }, 261 + wasm_bytes, 262 + manifest: None, 263 + }; 264 + 265 + registry.register(plugin).await; 266 + 267 + let mut secrets = Secrets::new(); 268 + secrets.insert("API_KEY".to_string(), "test-fake-key".to_string()); 269 + 270 + let mut instance = executor 271 + .instantiate( 272 + "steam", 273 + "user:did:plc:test", 274 + secrets, 275 + serde_json::Value::Null, 276 + ) 277 + .await 278 + .expect("Failed to instantiate Steam plugin"); 279 + 280 + // get_profile calls host_get_secret("API_KEY") internally. 281 + // It will then try to call the Steam API with the fake key, which will fail 282 + // with an HTTP error — but the important thing is it does NOT fail with 283 + // MISSING_SECRET, which would mean host_get_secret's JSON envelope is broken. 284 + let result = instance 285 + .call_get_profile("76561198024344834", &serde_json::Value::Null) 286 + .await; 287 + 288 + match result { 289 + Ok(_) => {} // Unlikely with a fake key, but fine 290 + Err(ExecutionError::PluginError { code, .. }) => { 291 + // HTTP_ERROR = Steam API rejected our fake key (expected) 292 + // INVALID_RESPONSE = Steam returned unexpected format (also fine) 293 + // MISSING_SECRET = host_get_secret envelope is broken (BAD!) 294 + assert_ne!( 295 + code, "MISSING_SECRET", 296 + "host_get_secret JSON envelope is broken — plugin couldn't parse the secret" 297 + ); 298 + } 299 + Err(e) => { 300 + panic!("Unexpected error type: {:?}", e); 301 + } 302 + } 303 + }