mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 11:09:34 +00:00
feat: integrate Pi backend and Pi Web
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
async function loadSubject() {
|
||||
return import("./request-security.ts");
|
||||
}
|
||||
|
||||
test("allows same-origin and non-browser API requests", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
assert.equal(isApiRequestAllowed(new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "localhost:30141",
|
||||
origin: "http://localhost:30141",
|
||||
"sec-fetch-site": "same-origin",
|
||||
},
|
||||
})), true);
|
||||
assert.equal(isApiRequestAllowed(new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: { host: "localhost:30141" },
|
||||
})), true);
|
||||
});
|
||||
|
||||
test("allows LAN same-origin requests when Next.js uses an internal localhost URL", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
const request = new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "192.168.32.7:30141",
|
||||
origin: "http://192.168.32.7:30141",
|
||||
"sec-fetch-site": "same-origin",
|
||||
},
|
||||
});
|
||||
assert.equal(isApiRequestAllowed(request), true);
|
||||
});
|
||||
|
||||
test("allows IPv6 and an explicitly configured hostname", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
const ipv6 = new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "[::1]:30141",
|
||||
origin: "http://[::1]:30141",
|
||||
"sec-fetch-site": "same-origin",
|
||||
},
|
||||
});
|
||||
const configured = new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "pi-web.internal:30141",
|
||||
origin: "http://pi-web.internal:30141",
|
||||
"sec-fetch-site": "same-origin",
|
||||
},
|
||||
});
|
||||
assert.equal(isApiRequestAllowed(ipv6), true);
|
||||
assert.equal(isApiRequestAllowed(configured, ["pi-web.internal"]), true);
|
||||
});
|
||||
|
||||
test("rejects cross-origin browser API requests", async () => {
|
||||
const { isApiRequestAllowed, shouldCheckApiRequestOrigin } = await loadSubject();
|
||||
const post = new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "localhost:30141",
|
||||
origin: "https://attacker.example",
|
||||
"sec-fetch-site": "cross-site",
|
||||
},
|
||||
});
|
||||
const crossSiteGet = new Request("http://localhost:30141/api/sessions", {
|
||||
headers: { host: "localhost:30141", "sec-fetch-site": "cross-site" },
|
||||
});
|
||||
assert.equal(shouldCheckApiRequestOrigin(post), true);
|
||||
assert.equal(isApiRequestAllowed(post), false);
|
||||
assert.equal(shouldCheckApiRequestOrigin(crossSiteGet), true);
|
||||
assert.equal(isApiRequestAllowed(crossSiteGet), false);
|
||||
});
|
||||
|
||||
test("allows only user-initiated session export document navigations from a PWA", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
const navigationHeaders = {
|
||||
host: "127.0.0.1:30141",
|
||||
"sec-fetch-site": "cross-site",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-user": "?1",
|
||||
};
|
||||
|
||||
assert.equal(isApiRequestAllowed(new Request(
|
||||
"http://127.0.0.1:30141/api/sessions/session-id/export?inline=1",
|
||||
{ headers: navigationHeaders },
|
||||
)), true);
|
||||
assert.equal(isApiRequestAllowed(new Request(
|
||||
"http://127.0.0.1:30141/api/sessions",
|
||||
{ headers: navigationHeaders },
|
||||
)), false);
|
||||
assert.equal(isApiRequestAllowed(new Request(
|
||||
"http://127.0.0.1:30141/api/sessions/session-id/export?inline=1",
|
||||
{ headers: { ...navigationHeaders, "sec-fetch-dest": "empty" } },
|
||||
)), false);
|
||||
assert.equal(isApiRequestAllowed(new Request(
|
||||
"http://127.0.0.1:30141/api/sessions/session-id/export?inline=1",
|
||||
{
|
||||
headers: {
|
||||
...navigationHeaders,
|
||||
"sec-fetch-user": "",
|
||||
},
|
||||
},
|
||||
)), false);
|
||||
assert.equal(isApiRequestAllowed(new Request(
|
||||
"http://127.0.0.1:30141/api/sessions/session-id/export?inline=1",
|
||||
{ method: "POST", headers: navigationHeaders },
|
||||
)), false);
|
||||
assert.equal(isApiRequestAllowed(new Request(
|
||||
"http://127.0.0.1:30141/api/sessions/session-id/export?inline=1",
|
||||
{ headers: { ...navigationHeaders, host: "attacker.example:30141" } },
|
||||
)), false);
|
||||
});
|
||||
|
||||
test("rejects an origin that does not match the external request host", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
const request = new Request("http://localhost:30141/api/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "192.168.32.7:30141",
|
||||
origin: "http://attacker.example",
|
||||
"sec-fetch-site": "same-site",
|
||||
},
|
||||
});
|
||||
assert.equal(isApiRequestAllowed(request), false);
|
||||
});
|
||||
|
||||
test("rejects DNS rebinding even when browser headers say same-origin", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
const request = new Request("http://localhost:30141/api/skills/install", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
host: "attacker.example:30141",
|
||||
origin: "http://attacker.example:30141",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"content-type": "application/json",
|
||||
},
|
||||
});
|
||||
assert.equal(isApiRequestAllowed(request), false);
|
||||
});
|
||||
|
||||
test("rejects missing, malformed, and unconfigured Host headers", async () => {
|
||||
const { isApiRequestAllowed } = await loadSubject();
|
||||
assert.equal(isApiRequestAllowed(new Request("http://localhost:30141/api/test")), false);
|
||||
assert.equal(isApiRequestAllowed(new Request("http://localhost:30141/api/test", {
|
||||
headers: { host: "localhost@attacker.example:30141" },
|
||||
})), false);
|
||||
assert.equal(isApiRequestAllowed(new Request("http://localhost:30141/api/test", {
|
||||
headers: { host: "pi-web.internal:30141" },
|
||||
})), false);
|
||||
});
|
||||
|
||||
test("recognizes JSON request content types", async () => {
|
||||
const { hasJsonContentType } = await loadSubject();
|
||||
assert.equal(hasJsonContentType(new Request("http://localhost", {
|
||||
headers: { "content-type": "application/json; charset=utf-8" },
|
||||
})), true);
|
||||
assert.equal(hasJsonContentType(new Request("http://localhost", {
|
||||
headers: { "content-type": "application/problem+json" },
|
||||
})), true);
|
||||
assert.equal(hasJsonContentType(new Request("http://localhost", {
|
||||
headers: { "content-type": "text/plain" },
|
||||
})), false);
|
||||
});
|
||||
Reference in New Issue
Block a user