1919 */
2020class Handler {
2121
22- /** @var string Ingest DSN URL. */
22+ /** @var string Original DSN as passed by the caller (used for browser JS init). */
23+ private string $ full_dsn ;
24+
25+ /** @var string Ingest endpoint URL (DSN without the trailing API key segment). */
2326 private string $ dsn ;
2427
28+ /** @var string API key extracted from DSN, sent as X-API-Key header. */
29+ private string $ api_key ;
30+
2531 /** @var string Environment name. */
2632 private string $ env ;
2733
@@ -70,7 +76,13 @@ class Handler {
7076 * @param bool $track_vitals Whether to inject the browser vitals JS bundle.
7177 */
7278 public function __construct ( string $ dsn , string $ env = 'production ' , ?string $ release = null , bool $ track_vitals = true ) {
73- $ this ->dsn = $ dsn ;
79+ // Store the original DSN for browser JS init (browser SDK parses it itself).
80+ $ this ->full_dsn = $ dsn ;
81+ // Extract the API key from the DSN path so it is sent as X-API-Key header
82+ // rather than embedded in the URL (prevents leakage in server/CDN logs).
83+ $ parts = explode ( '/ ' , rtrim ( $ dsn , '/ ' ) );
84+ $ this ->api_key = array_pop ( $ parts );
85+ $ this ->dsn = implode ( '/ ' , $ parts );
7486 $ this ->env = $ env ;
7587 $ this ->release = $ release ;
7688 $ this ->track_vitals = $ track_vitals ;
@@ -203,7 +215,7 @@ public function enqueue_vitals_script(): void {
203215 * @param array $options
204216 */
205217 $ options = apply_filters ( 'devpulse_vitals_options ' , [
206- 'dsn ' => $ this ->dsn ,
218+ 'dsn ' => $ this ->full_dsn , // browser SDK needs the full DSN (including key segment)
207219 'environment ' => $ this ->env ,
208220 'release ' => $ this ->release ,
209221 'trackVitals ' => true ,
@@ -223,7 +235,7 @@ public function enqueue_vitals_script(): void {
223235
224236 /** @since 1.0.0 */
225237 public function get_dsn (): string {
226- return $ this ->dsn ;
238+ return $ this ->full_dsn ; // returns the original DSN as provided by the caller
227239 }
228240
229241 /** @since 1.0.0 */
@@ -267,7 +279,7 @@ public function send_test(): bool {
267279 $ response = wp_remote_post ( $ this ->dsn , [
268280 'timeout ' => 5 , // longer timeout: user is waiting for feedback
269281 'blocking ' => true , // must be blocking so we can read the status code
270- 'headers ' => [ 'Content-Type ' => 'application/json ' ],
282+ 'headers ' => [ 'Content-Type ' => 'application/json ' , ' X-API-Key ' => $ this -> api_key ],
271283 'body ' => $ json ,
272284 'data_format ' => 'body ' ,
273285 ] );
@@ -784,7 +796,7 @@ private function send( array $payload ): bool {
784796 $ response = wp_remote_post ( $ this ->dsn , [
785797 'timeout ' => 2 ,
786798 'blocking ' => false ,
787- 'headers ' => [ 'Content-Type ' => 'application/json ' ],
799+ 'headers ' => [ 'Content-Type ' => 'application/json ' , ' X-API-Key ' => $ this -> api_key ],
788800 'body ' => $ json ,
789801 'data_format ' => 'body ' ,
790802 ] );
@@ -808,7 +820,7 @@ private function send( array $payload ): bool {
808820 $ context = stream_context_create ( [
809821 'http ' => [
810822 'method ' => 'POST ' ,
811- 'header ' => "Content-Type: application/json \r\n" ,
823+ 'header ' => "Content-Type: application/json \r\n X-API-Key: { $ this -> api_key }\r\ n" ,
812824 'content ' => $ json ,
813825 'timeout ' => 2 ,
814826 'ignore_errors ' => true ,
@@ -822,7 +834,7 @@ private function send( array $payload ): bool {
822834 return true ;
823835 } );
824836 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
825- $ result = file_get_contents ( $ this ->dsn , false , $ context );
837+ $ result = file_get_contents ( $ this ->dsn , false , $ context ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
826838 restore_error_handler ();
827839
828840 if ( $ stream_error !== null ) {
0 commit comments