Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install docker-compose
uses: KengoTODA/actions-setup-docker-compose@v1
with:
version: '2.14.2'
- name: Run Tests
run: sudo CONFIG_FILE=./env/local.js docker compose -f docker-compose-with-keycloak.yml up --abort-on-container-exit
run: sudo CONFIG_FILE=./env/docker-tests.js docker compose -f docker-compose-run-tests.yml up --abort-on-container-exit
125 changes: 115 additions & 10 deletions api/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,51 @@
});

/**
* System healthcheck
* Retrieve Claims Description.
* @route GET /claimdescription
* @group Metadata - Support operations
* @returns {HealthcheckResponse.model} 200 - Claim Description Response
* @returns {Error.model} 400 - Syntax error
* @returns {Error.model} 500 - Unexpected error
*/
app.get('/claimdescription', function(req, res) {
fetch("https://www.iana.org/assignments/jwt/jwt.xml")
.then((response) => {
response.text()
.then( (text) => {
log.debug("Retrieved: " + text);
res
.append('Content-Type', 'application/xml')
.send(text)
try {
fetch("https://www.iana.org/assignments/jwt/jwt.xml")
.then((response) => {
response.text()
.then( (text) => {
log.debug("Retrieved: " + text);
res
.append('Content-Type', 'application/xml')
.send(text)
});
})
.catch(function (error) {
log.error('Error from claimsdescription endpoint: ' + error);
if(!!error.response) {
if(!!error.response.status) {
log.error("Error Status: " + error.response.status);
}
if(!!error.response.data) {
log.error("Error Response body: " + JSON.stringify(error.response.data));
}
if(!!error.response.headers) {
log.error("Error Response headers: " + error.response.headers);
}
if (!!error.response) {
res.status(error.response.status);
res.json(error.response.data);
} else {
res.status(500);
res.json(error.message);
}
}
});
});
} catch(e) {
log.error("An error occurred while retrieving the claim description XML: " + e.stack);
res.status(500)
.render('error', { error: e });

Check warning

Code scanning / CodeQL

Information exposure through a stack trace Medium

This information exposed to the user depends on
stack trace information
.

Copilot Autofix

AI about 1 month ago

To fix the information exposure via stack trace, we should ensure that information sent to the user contains only generic error details, while any detailed error logs (including stack traces) are retained only in server logs. Specifically:

  • In file api/server.js, at line 119, replace res.render('error', { error: e }); with a more generic response.
  • Log the complete error (including stack trace) server-side using log.error, as is already done.
  • For the user response, send only a general message, such as "An unexpected error occurred." or a structured error object containing a generic message and sanitized code, but never expose stack traces or internal exception details.
  • If standardized error codes or models are used (such as Swagger), ensure the structure matches expectations, but content remains generic.

No change to imports is required; use only standard logging.


Suggested changeset 1
api/server.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/api/server.js b/api/server.js
--- a/api/server.js
+++ b/api/server.js
@@ -116,7 +116,11 @@
   } catch(e) {
     log.error("An error occurred while retrieving the claim description XML: " + e.stack);
     res.status(STATUS_500)
-       .render('error', { error: e });
+       .json({
+         status: false,
+         code: 'UNEXPECTED_ERROR',
+         message: 'An unexpected error occurred.'
+       });
   }
 });
 
EOF
@@ -116,7 +116,11 @@
} catch(e) {
log.error("An error occurred while retrieving the claim description XML: " + e.stack);
res.status(STATUS_500)
.render('error', { error: e });
.json({
status: false,
code: 'UNEXPECTED_ERROR',
message: 'An unexpected error occurred.'
});
}
});

Copilot is powered by AI and may make mistakes. Always verify output.
}
});

/**
Expand Down Expand Up @@ -181,6 +208,84 @@
}
});

/**
* @typedef IntrspectionRequest
* @property {string} grant_type.required - The OAuth2 / OIDC Grant / Flow Type
* @property {string} client_id.required - The OAuth2 client identifier
*/

/**
* @typedef IntrospectionResponse
* @property {string} access_token.required - The OAuth2 Access Token
* @property {string} id_token - The OpenID Connect ID Token
*/

/**
* Wrapper around OAuth2 Introspection Endpoint
* @route POST /introspection
* @group Debugger - Operations for OAuth2/OIDC Debugger
* @param {IntrospectionRequest.model} req.body.required - Token Endpoint Request
* @returns {IntrospectionResponse.model} 200 - Token Endpoint Response
* @returns {Error.model} 400 - Syntax error
* @returns {Error.model} 500 - Unexpected error
*/
app.post('/introspection', (req, res) => {
try {
log.info('Entering app.post for /introspection.');
const body = req.body;
log.debug('body: ' + JSON.stringify(body));
var headers = {
"Authorization": req.headers.authorization,
"Content-Type": "application/x-www-form-urlencoded"
};
var introspectionRequestMessage = {
token: body.token,
token_type_hint: body.token_type_hint
}
const parameterString = JSON.stringify(introspectionRequestMessage);
log.debug("Method: POST");
log.debug("URL: " + body.introspectionEndpoint);
log.debug("headers: " + JSON.stringify(headers));
log.debug("body: " + parameterString);
axios({
method: 'post',
url: body.introspectionEndpoint,
headers: headers,
data: introspectionRequestMessage,
httpsAgent: new (require('https').Agent)({ rejectUnauthorized: true })
})
.then(function (response) {
log.debug('Response from OAuth2 Introspection Endpoint: ' + JSON.stringify(response.data));
log.debug('Headers: ' + response.headers);
res.status(response.status);
res.json(response.data);
})
.catch(function (error) {
log.error('Error from OAuth2 Introspection Endpoint: ' + error);
if(!!error.response) {
if(!!error.response.status) {
log.error("Error Status: " + error.response.status);
}
if(!!error.response.data) {
log.error("Error Response body: " + JSON.stringify(error.response.data));
}
if(!!error.response.headers) {
log.error("Error Response headers: " + error.response.headers);
}
if (!!error.response) {
res.status(error.response.status);
res.json(error.response.data);
} else {
res.status(500);
res.json(error.message);
}
}
});
} catch(e) {
log.error("Error from OAuth2 Introspection Endpoint: " + error);
}
});

let options = {
swaggerDefinition: {
info: {
Expand Down
6 changes: 3 additions & 3 deletions client/public/debugger2.html
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
<div class="step3" id="step3">
<legend id="h2_title_2" name="h2_title_2">Exchange Authorization Code for Access Token
</legend>
<form id="step0_expand_form">
<form id="step3_expand_form">
<input class="btn2" type="submit" name="token_expand_button" id="token_expand_button" value="Hide" onclick="return debugger2.onClickShowFieldSet('token_expand_button', 'token_fieldset');"/>
</form>
<fieldset id="token_fieldset" name="token_fieldset" style="display: block;">
Expand Down Expand Up @@ -452,7 +452,7 @@
<input class="btn2" type="submit" name="logout_expand_button" id="logout_expand_button" value="Hide" onclick="return debugger2.onClickShowFieldSet('logout_expand_button', 'logout_fieldset');"/>
</form>
<fieldset id="logout_fieldset" name="logout_fieldset" style="display: block;">
<form>
<form action="" onsubmit="return false;">
<table>
<tbody>
<tr>
Expand Down Expand Up @@ -494,7 +494,7 @@
</tr>
<tr>
<td>
<input id="logout_btn" class="logout_btn" type="submit" value="Logout" />
<input id="logout_btn" class="logout_btn" type="submit" value="Logout" onclick="return debugger2.logoutButtonClick();" />
</td>
<td>&nbsp;
</td>
Expand Down
10 changes: 10 additions & 0 deletions client/public/introspection.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@
<input class="stored" id="introspection_endpoint" name="introspection_endpoint" type="text" max="150" />
</td>
</tr>
<tr>
<td>
<div class="tooltip"><label>Initiate Introspection Endpoint Call From front or backend.: </label><span class="tooltiptext">The debugger can initiate an Introspection Endpoint call from either the frontend (browser) or from the backend API component. Certain IdPs make stringent assumptions about CORS and how calls will be originated. Especially concerning the Origin request header, which cannot be controlled from the browser.</span>
</div>
</td>
<td>Front
<input id="introspection_initiateFromFrontEnd" name="introspection-initiateFromFrontEnd" checked="true" onclick="introspection.setInitiateFromEnd('frontend');" type="radio" />Back
<input id="introspection_initiateFromBackEnd" name="introspection-initiateFromFrontEnd" checked="false" onclick="introspection.setInitiateFromEnd('backend');" type="radio" />
</td>
</tr>
<tr>
<td>
<div class="tooltip"><label>OIDC Introspection Token: </label><span class="tooltiptext">The OIDC Introspection Token.</span>
Expand Down
4 changes: 2 additions & 2 deletions client/public/token_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
<legend id="h2_title_2" name="h2_title_2">Decoded Token
</legend>
<div class="tab">
<button class="tablinks" onclick="token_detail.populateTable(event, 'json')">JSON</button>
<button class="tablinks" onclick="token_detail.populateTable(event, 'key-pair')">Key Pairs</button>
<button id="json_view_button" class="tablinks" onclick="token_detail.populateTable(event, 'json')">JSON</button>
<button id="key_pair_button" class="tablinks" onclick="token_detail.populateTable(event, 'key-pair')">Key Pairs</button>
</div>
<div id="json" class="tabcontent" style="display: block;">
<table class="outertable">
Expand Down
Loading
Loading