The stack trace that reached the client.
SI-11 is the NIST 800-53 control about the body a handler returns when a request fails. A caller who hit a 500 needs to know the request failed. They do not need the stack, the query, or the path that produced it. Those fields sit on a diagnostic view, and you decide who can open it. What usually shows up in a pull request is smaller than that decision: the handler copies the logged fields onto the response. A diff can show the copy. It cannot show that list, and it cannot show who can reach the route.
The shapes the same control failure takes.
SI-11 weakens when a change puts internal failure detail where a caller can read it. The recurring shapes:
The response starts carrying the stack
A handler that returned a generic failure now includes the exception message and the stack, so whoever called the endpoint sees how the code got there.
The query comes back with the error
A database failure returns the SQL text, the bind values, or the schema names to the caller, which is more than the next request needs.
Verbose errors stay on in production
A debug flag, a framework setting, or an environment variable that prints full errors is left on for the production build, so every failure becomes a detailed dump.
A secret rides out inside the message
The exception text includes a connection string, a token, or a key that was in scope when the call failed, and the response forwards that text unchanged.
Internal layout is added to the body
File paths, hostnames, container ids, or dependency versions are added to the error body the caller sees, sketching the system behind the request.
A 500 that starts returning the stack and the SQL.
The handler already passes the exception and the query to log.error. The change replaces the generic 500 body with err.message, err.stack, and sql. The only annotation on the new line is a ticket id. Nothing in the diff limits who can call the route.
function onFailure(err, sql) { log.error({ err, sql });- return Response.json({ error: "request failed" }, { status: 500 });+ return Response.json({ error: err.message, stack: err.stack, sql }, { status: 500 }); // TCK-1904}The return now builds the JSON body from err.message, err.stack, and sql. When that line runs, the caller receives the exception text, the call stack, and the query. The log.error line above already records err and sql, and the diff does not remove it. What the diff adds is those values on the body that goes back to the caller. The diff cannot show who can reach this handler. Return the generic message to the caller. Leave the message, the stack, and the SQL in the log.
Error handling is tested by what a caller actually receives.
An assessor sampling SI-11 can send a failing request and read the body, compare that body with what the server logged, and check who can open the detailed view. A handler that starts putting the stack and the query on the response is the kind of change that sample is built to find, and the body is assembled in a diff. The diff shows how the message is built. It does not name who is allowed to open the detailed view.
A review, not a live error page.
heygrc flags changes that put internal failure detail on a response and cites SI-11 so the fix happens in the pull request. It does not call your API to read the live error, and it does not know which people your organization named. It catches the moment the detail moves onto the response, at the diff.