Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Sentry NodeJS setUser per request #13205

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import*asSentryfrom'@sentry/node';importbodyParserfrom'body-parser';importexpress,{Application,NextFunction,Request,Response,Router}from'express';Sentry.init({dsn: undefined,// not needed for local debugging to reveal the issuebeforeSend: (event,hint, ...args)=>{const{ type, contexts, exception, extra, tags, message, user, request }=event;console.dir({whoami: 'sentry:beforeSend',event: { type, contexts, exception, extra, tags, message, user, request },
hint,
args
},{depth: null});returnevent;},skipOpenTelemetrySetup: true});constapp: Application=express();constrouter=Router();router.use(bodyParser.urlencoded({extended: true,limit: '500kb'}));router.use(bodyParser.json({limit: '500kb'}));constUsers: {id: string;email: string;name: string}[]=[{id: '1',email: 'foo@example.com',name: 'foo example'},{id: '2',email: 'foo2@example.com',name: 'foo example2'},{id: '3',email: 'foo3@example.com',name: 'foo example3'},{id: '4',email: 'foo4@example.com',name: 'foo example4'}];router.use('/users',function(req,res,next){try{constauthUser=Users.find((u)=>u.id===req.headers['authorization']);if(authUser){Sentry.setTag('Authenticated',true);Sentry.setUser(authUser);res.json(Users);}else{thrownewError('Authentication Error');}}catch(err){next(err);}});app.use('/api',router);app.use(function(err: Error,req: Request,res: Response,next: NextFunction){const{ method, originalUrl, params, query, body }=req;const{ statusCode, locals }=res;Sentry.withScope((scope)=>{scope.setExtras({request: { method, originalUrl, params, query, body },response: { statusCode, locals }});consteventId=Sentry.captureException(err);(resas{sentry?: string}).sentry=eventId;});next(err);});// Or just use this, which is identical to above, without `extras`// Sentry.setupExpressErrorHandler(app);constPORT=process.env.PORT||3000app.listen(PORT,()=>{console.log(`API running @ http://localhost:${PORT}`);});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users
# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions