agent-dag 1.33.95 → 1.33.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>agents-deck</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='84' font-size='84'%3E%E2%97%89%3C/text%3E%3C/svg%3E" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-mMOusrOQ.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-dwRx-2y0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.97",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch parallel subagents fork, call tools, and return on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/server/index.mjs
CHANGED
|
@@ -1696,16 +1696,55 @@ export function requestUrl(rawUrl) {
|
|
|
1696
1696
|
// opaque `null` origin (sandboxed iframe, data: URL) fails
|
|
1697
1697
|
// to parse and is refused with it.
|
|
1698
1698
|
//
|
|
1699
|
-
//
|
|
1700
|
-
//
|
|
1701
|
-
//
|
|
1702
|
-
//
|
|
1703
|
-
//
|
|
1699
|
+
// Agreement between the two is necessary but not sufficient, because both are
|
|
1700
|
+
// derived from the URL the page was served from and neither says a word about
|
|
1701
|
+
// the address the socket actually landed on — so the Host must also name a
|
|
1702
|
+
// loopback identity. See isLoopbackHost for the attack that gets through
|
|
1703
|
+
// without it.
|
|
1704
|
+
//
|
|
1705
|
+
// A request carrying neither header is not a browser request and is allowed
|
|
1706
|
+
// whatever Host it names: that is hook/hook.js, a plain Node http.request from
|
|
1707
|
+
// the user's own machine that sends no Origin at all, plus curl and the deck's
|
|
1708
|
+
// own tooling. Ambient browser authority is the whole threat here, and those
|
|
1709
|
+
// clients have none — a process that can POST here can already run anything as
|
|
1710
|
+
// the user, and nothing it sends is chosen by a page.
|
|
1704
1711
|
export function isTrustedMutation({ origin, host, secFetchSite } = {}) {
|
|
1705
1712
|
const site = typeof secFetchSite === "string" ? secFetchSite.trim().toLowerCase() : "";
|
|
1706
1713
|
if (site && site !== "same-origin" && site !== "none") return false;
|
|
1707
|
-
|
|
1708
|
-
|
|
1714
|
+
const hasOrigin = typeof origin === "string" && origin !== "";
|
|
1715
|
+
if (!hasOrigin && !site) return true;
|
|
1716
|
+
// Either header present means a browser sent this, so the rebinding gate
|
|
1717
|
+
// applies even to the shape that carries fetch metadata but no Origin.
|
|
1718
|
+
if (!isLoopbackHost(host)) return false;
|
|
1719
|
+
return hasOrigin ? originMatchesHost(origin, host) : true;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// Was this request addressed to a name that can only ever be this machine?
|
|
1723
|
+
//
|
|
1724
|
+
// Origin === Host alone is not a defence against DNS rebinding: the page is
|
|
1725
|
+
// served from http://attacker.example:4317, the attacker re-points that record
|
|
1726
|
+
// at 127.0.0.1, and the browser then sends Host: attacker.example:4317 with a
|
|
1727
|
+
// matching Origin and Sec-Fetch-Site: same-origin — every header self-consistent
|
|
1728
|
+
// and every one attacker-chosen, because fetch metadata comes from the origin
|
|
1729
|
+
// tuple (scheme, host, port), not from the resolved IP. The browser also treats
|
|
1730
|
+
// the reply as same-origin, so the page reads the body: that is the account
|
|
1731
|
+
// share envelope, with the OAuth token in it. A loopback literal has no DNS
|
|
1732
|
+
// record to re-point, and `localhost` is reserved to loopback, so requiring one
|
|
1733
|
+
// of them is what separates the deck's own UI from the rebound page.
|
|
1734
|
+
function isLoopbackHost(host) {
|
|
1735
|
+
if (typeof host !== "string") return false;
|
|
1736
|
+
const authority = host.trim().toLowerCase();
|
|
1737
|
+
// A real Host header is bare authority. Userinfo or a path would let
|
|
1738
|
+
// `evil.example@127.0.0.1` and `127.0.0.1/…` parse to a loopback hostname
|
|
1739
|
+
// while naming something else, so refuse them rather than reason about them.
|
|
1740
|
+
if (authority === "" || /[/\\?#@\s]/.test(authority)) return false;
|
|
1741
|
+
let name;
|
|
1742
|
+
try { name = new URL(`http://${authority}`).hostname; } catch { return false; }
|
|
1743
|
+
if (name === "localhost" || name === "[::1]") return true;
|
|
1744
|
+
// The whole 127.0.0.0/8 is this machine, not just .0.1 — a second deck parked
|
|
1745
|
+
// on 127.0.0.2 is as local as the first. URL only produces a dotted quad for
|
|
1746
|
+
// something it already validated as an IPv4 address, so shape is enough here.
|
|
1747
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(name);
|
|
1709
1748
|
}
|
|
1710
1749
|
|
|
1711
1750
|
// Does `origin` name the same host:port the request was addressed to? Ports are
|