Heroku
tcp/443
Exposing Swagger/OpenAPI documentation is primarily a risk if your API has underlying security flaws, as it gives attackers a precise roadmap to find them.
Those detail every endpoint, parameter, and data model, making it easier to discover and exploit vulnerabilities like broken access control or injection points.
While a perfectly secure API mitigates the danger, protecting your documentation is a critical layer of defense that forces attackers to work without a map.
Severity: info
Fingerprint: 5733ddf49ff49cd151e75e4befe4071ff647dd15f48e2c105a9b35eb87cf22c7
Public Swagger UI/API detected at path: /v3/api-docs - sample paths:
DELETE /list/share/{token}
DELETE /list/{id}
DELETE /list/{listId}/item/{listItemId}
GET /accounts/{id}
GET /api/user
GET /list
GET /list/accept/{token}
GET /list/{listId}
GET /server/health
GET /server/info
GET /server/status
PATCH /list/{listId}/item/{listItemId}/complete
POST /list/{listId}/item
POST /list/{listId}/share
Open service 75.2.97.79:443 · mut-ink.powersofeight.com
2026-01-09 13:53
HTTP/1.1 503 Service Unavailable
Cache-Control: no-cache, no-store
Content-Type: text/html; charset=utf-8
Nel: {"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}
Report-To: {"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=Z%2F2Ae7LNmJauSLnKu7M%2B2LFjjFysSD2p4KnJEV%2BCZ8A%3D\u0026sid=c46efe9b-d3d2-4a0c-8c76-bfafa16c5add\u0026ts=1767966824"}],"max_age":3600}
Reporting-Endpoints: heroku-nel="https://nel.heroku.com/reports?s=Z%2F2Ae7LNmJauSLnKu7M%2B2LFjjFysSD2p4KnJEV%2BCZ8A%3D&sid=c46efe9b-d3d2-4a0c-8c76-bfafa16c5add&ts=1767966824"
Server: Heroku
Via: 1.1 heroku-router
Date: Fri, 09 Jan 2026 13:53:48 GMT
Content-Length: 567
Connection: close
Page title: Application Error
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>Application Error</title>
<style media="screen">
html,body,iframe {
margin: 0;
padding: 0;
}
html,body {
height: 100%;
overflow: hidden;
}
iframe {
width: 100%;
height: 100%;
border: 0;
}
</style>
</head>
<body>
<iframe src="https://www.herokucdn.com/error-pages/application-error.html"></iframe>
</body>
</html>
Open service 75.2.97.79:443 · mut-ink.powersofeight.com
2026-01-02 12:45
Open service 75.2.97.79:443 · mut-ink.powersofeight.com
2025-12-22 17:56
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-store
Content-Language: en
Content-Length: 6441
Content-Type: text/html;charset=UTF-8
Date: Mon, 22 Dec 2025 17:57:14 GMT
Last-Modified: Thu, 01 Jan 1970 00:00:01 GMT
Nel: {"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}
Report-To: {"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=XocF7HDyOiFI7y6CT6yoHz5EN6nQ7vgig5mL0QjcgAs%3D\u0026sid=c46efe9b-d3d2-4a0c-8c76-bfafa16c5add\u0026ts=1766426234"}],"max_age":3600}
Reporting-Endpoints: heroku-nel="https://nel.heroku.com/reports?s=XocF7HDyOiFI7y6CT6yoHz5EN6nQ7vgig5mL0QjcgAs%3D&sid=c46efe9b-d3d2-4a0c-8c76-bfafa16c5add&ts=1766426234"
Server: Heroku
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Via: 1.1 heroku-router
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 0
Connection: close
Page title: MutInc - Mutual Inclusion
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MutInc - Mutual Inclusion</title>
<!-- Tailwind CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Preact / HTM -->
<script src="https://unpkg.com/preact@10.5.15/dist/preact.umd.js"></script>
<script src="https://unpkg.com/preact@10.5.15/hooks/dist/hooks.umd.js"></script>
<script src="https://unpkg.com/htm@3.1.0/dist/htm.umd.js"></script>
<!-- FontAwesome -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"/>
<!-- SockJS and STOMP for WebSocket -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<!-- Import Map -->
<script type="importmap">
{
"imports": {
"lists": "/js/lists.js",
"account": "/js/account.js",
"list-detail": "/js/list_detail.js",
"modal": "/js/modal.js",
"websocket": "/js/web_socket_list_manager.js",
"user-pill": "/js/user_pill.js",
"notification_banner": "/js/notification_banner.js",
"sharing": "/js/sharing.js"
}
}
</script>
<style>
.modal {
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-indigo-400 to-purple-600 text-gray-900">
<div id="banner-root" class="w-full"></div>
<div id="app" class="w-full">
</div>
<!--WebSocket init script-->
<script type="module">
import { initWebSocket } from "websocket";
import { showNotification } from "notification_banner";
const {h, render} = preact;
const html = htm.bind(h);
initWebSocket()
.then(console.log("WebSocket connected successfully!"))
.catch(err => {
console.error("Failed to connect to WebSocket:", err);
// render(
// html`
// <${Notification}
// message="Problem connecting to the server. Please refresh the page after some time or try again later."
// type="error"
// />`,
// document.body
// );
showNotification("Problem connecting to the server. Please refresh the page after some time or try again later.");
});
</script>
<!-- APP SCRIPT -->
<script type="module">
const { h, render } = preact;
const {useState, useEffect} = preactHooks;
const html = htm.bind(h);
import ListsPage from "lists";
import AccountPage from "account";
import ListDetailPage from "list-detail";
import UserPill from "user-pill";
import {showNotification} from "notification_banner";
import {ShareList} from "sharing";
const getQueryParams = () => {
const params = new URLSearchParams(window.location.search);
return Object.fromEntries(params.entries());
};
const App = () => {
const [page, setPage] = useState("lists");
const [currentList, setCurrentList] = useState(null);
const {error, note, v, share} = getQueryParams();
// FIXED NAVIGATION (unwrap wrapped list object)
const navigate = (p, data = null) => {
if (data && data.list) {
setCurrentList(data.list); // unwrap
} else {
setCurrentList(data);
}
setPage(p);
let validPages = ["lists", "account", "list-detail", "share"];
const state = {page: validPages.includes(p) ? p : "lists", list: data};
window.history.pushState(state, '', window.location.pathname + '?p=' + p + (data?.list ? `&v=${data.list.id}` : ''));
};
let content;
if (page === "lists") {
content = html`<${ListsPage} navigat
Open service 75.2.97.79:443 · mut-ink.powersofeight.com
2025-12-20 20:43
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-store
Content-Language: en
Content-Length: 6441
Content-Type: text/html;charset=UTF-8
Date: Sat, 20 Dec 2025 20:43:47 GMT
Last-Modified: Thu, 01 Jan 1970 00:00:01 GMT
Nel: {"report_to":"heroku-nel","response_headers":["Via"],"max_age":3600,"success_fraction":0.01,"failure_fraction":0.1}
Report-To: {"group":"heroku-nel","endpoints":[{"url":"https://nel.heroku.com/reports?s=w3wcYLikv0MQuccaHyKAFA2tI7L6CS5YCI%2F2SOCF3e8%3D\u0026sid=c46efe9b-d3d2-4a0c-8c76-bfafa16c5add\u0026ts=1766263426"}],"max_age":3600}
Reporting-Endpoints: heroku-nel="https://nel.heroku.com/reports?s=w3wcYLikv0MQuccaHyKAFA2tI7L6CS5YCI%2F2SOCF3e8%3D&sid=c46efe9b-d3d2-4a0c-8c76-bfafa16c5add&ts=1766263426"
Server: Heroku
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Via: 1.1 heroku-router
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 0
Connection: close
Page title: MutInc - Mutual Inclusion
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MutInc - Mutual Inclusion</title>
<!-- Tailwind CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Preact / HTM -->
<script src="https://unpkg.com/preact@10.5.15/dist/preact.umd.js"></script>
<script src="https://unpkg.com/preact@10.5.15/hooks/dist/hooks.umd.js"></script>
<script src="https://unpkg.com/htm@3.1.0/dist/htm.umd.js"></script>
<!-- FontAwesome -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"/>
<!-- SockJS and STOMP for WebSocket -->
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<!-- Import Map -->
<script type="importmap">
{
"imports": {
"lists": "/js/lists.js",
"account": "/js/account.js",
"list-detail": "/js/list_detail.js",
"modal": "/js/modal.js",
"websocket": "/js/web_socket_list_manager.js",
"user-pill": "/js/user_pill.js",
"notification_banner": "/js/notification_banner.js",
"sharing": "/js/sharing.js"
}
}
</script>
<style>
.modal {
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-indigo-400 to-purple-600 text-gray-900">
<div id="banner-root" class="w-full"></div>
<div id="app" class="w-full">
</div>
<!--WebSocket init script-->
<script type="module">
import { initWebSocket } from "websocket";
import { showNotification } from "notification_banner";
const {h, render} = preact;
const html = htm.bind(h);
initWebSocket()
.then(console.log("WebSocket connected successfully!"))
.catch(err => {
console.error("Failed to connect to WebSocket:", err);
// render(
// html`
// <${Notification}
// message="Problem connecting to the server. Please refresh the page after some time or try again later."
// type="error"
// />`,
// document.body
// );
showNotification("Problem connecting to the server. Please refresh the page after some time or try again later.");
});
</script>
<!-- APP SCRIPT -->
<script type="module">
const { h, render } = preact;
const {useState, useEffect} = preactHooks;
const html = htm.bind(h);
import ListsPage from "lists";
import AccountPage from "account";
import ListDetailPage from "list-detail";
import UserPill from "user-pill";
import {showNotification} from "notification_banner";
import {ShareList} from "sharing";
const getQueryParams = () => {
const params = new URLSearchParams(window.location.search);
return Object.fromEntries(params.entries());
};
const App = () => {
const [page, setPage] = useState("lists");
const [currentList, setCurrentList] = useState(null);
const {error, note, v, share} = getQueryParams();
// FIXED NAVIGATION (unwrap wrapped list object)
const navigate = (p, data = null) => {
if (data && data.list) {
setCurrentList(data.list); // unwrap
} else {
setCurrentList(data);
}
setPage(p);
let validPages = ["lists", "account", "list-detail", "share"];
const state = {page: validPages.includes(p) ? p : "lists", list: data};
window.history.pushState(state, '', window.location.pathname + '?p=' + p + (data?.list ? `&v=${data.list.id}` : ''));
};
let content;
if (page === "lists") {
content = html`<${ListsPage} navigat