@fourteensystems/shipguard 0.2.6 → 0.3.0
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/README.md +47 -5
- package/dist/engine/config.d.ts.map +1 -1
- package/dist/engine/config.js +2 -0
- package/dist/engine/config.js.map +1 -1
- package/dist/engine/report.d.ts.map +1 -1
- package/dist/engine/report.js +3 -0
- package/dist/engine/report.js.map +1 -1
- package/dist/engine/version.d.ts +1 -1
- package/dist/engine/version.js +1 -1
- package/dist/next/deps.js +1 -1
- package/dist/next/deps.js.map +1 -1
- package/dist/next/routes.d.ts +6 -1
- package/dist/next/routes.d.ts.map +1 -1
- package/dist/next/routes.js +50 -3
- package/dist/next/routes.js.map +1 -1
- package/dist/next/routes.test.js +66 -1
- package/dist/next/routes.test.js.map +1 -1
- package/dist/next/types.d.ts +10 -0
- package/dist/next/types.d.ts.map +1 -1
- package/dist/next/wrappers.js +62 -3
- package/dist/next/wrappers.js.map +1 -1
- package/dist/rules/auth-boundary-missing.d.ts.map +1 -1
- package/dist/rules/auth-boundary-missing.js +73 -41
- package/dist/rules/auth-boundary-missing.js.map +1 -1
- package/dist/rules/auth-boundary-missing.test.js +34 -10
- package/dist/rules/auth-boundary-missing.test.js.map +1 -1
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +42 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/input-validation-missing.d.ts +5 -0
- package/dist/rules/input-validation-missing.d.ts.map +1 -0
- package/dist/rules/input-validation-missing.js +272 -0
- package/dist/rules/input-validation-missing.js.map +1 -0
- package/dist/rules/input-validation-missing.test.d.ts +2 -0
- package/dist/rules/input-validation-missing.test.d.ts.map +1 -0
- package/dist/rules/input-validation-missing.test.js +449 -0
- package/dist/rules/input-validation-missing.test.js.map +1 -0
- package/dist/rules/rate-limit-missing.d.ts.map +1 -1
- package/dist/rules/rate-limit-missing.js +101 -54
- package/dist/rules/rate-limit-missing.js.map +1 -1
- package/dist/rules/rate-limit-missing.test.js +90 -34
- package/dist/rules/rate-limit-missing.test.js.map +1 -1
- package/dist/rules/wrapper-unrecognized.d.ts.map +1 -1
- package/dist/rules/wrapper-unrecognized.js +6 -1
- package/dist/rules/wrapper-unrecognized.js.map +1 -1
- package/dist/util/outbound-fetch.d.ts +14 -0
- package/dist/util/outbound-fetch.d.ts.map +1 -0
- package/dist/util/outbound-fetch.js +59 -0
- package/dist/util/outbound-fetch.js.map +1 -0
- package/dist/util/outbound-fetch.test.d.ts +2 -0
- package/dist/util/outbound-fetch.test.d.ts.map +1 -0
- package/dist/util/outbound-fetch.test.js +83 -0
- package/dist/util/outbound-fetch.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-fetch.test.d.ts","sourceRoot":"","sources":["../../src/util/outbound-fetch.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { detectOutboundFetcher } from "./outbound-fetch.js";
|
|
3
|
+
describe("detectOutboundFetcher", () => {
|
|
4
|
+
it("detects fetch() with user-influenced URL", () => {
|
|
5
|
+
const src = `
|
|
6
|
+
export async function GET(request: Request) {
|
|
7
|
+
const url = new URL(request.url).searchParams.get("target");
|
|
8
|
+
const response = await fetch(url);
|
|
9
|
+
return Response.json(await response.json());
|
|
10
|
+
}`;
|
|
11
|
+
const result = detectOutboundFetcher(src);
|
|
12
|
+
expect(result.hasOutboundFetch).toBe(true);
|
|
13
|
+
expect(result.hasUserInfluencedUrl).toBe(true);
|
|
14
|
+
expect(result.isRisky).toBe(true);
|
|
15
|
+
expect(result.evidence.length).toBeGreaterThanOrEqual(2);
|
|
16
|
+
});
|
|
17
|
+
it("detects axios with user-influenced URL", () => {
|
|
18
|
+
const src = `
|
|
19
|
+
export async function POST(request: Request) {
|
|
20
|
+
const body = await request.json();
|
|
21
|
+
const response = await axios.get(body.url);
|
|
22
|
+
return Response.json(response.data);
|
|
23
|
+
}`;
|
|
24
|
+
const result = detectOutboundFetcher(src);
|
|
25
|
+
expect(result.isRisky).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it("does NOT flag fetch with hardcoded URL", () => {
|
|
28
|
+
const src = `
|
|
29
|
+
export async function GET() {
|
|
30
|
+
const response = await fetch("https://api.example.com/data");
|
|
31
|
+
return Response.json(await response.json());
|
|
32
|
+
}`;
|
|
33
|
+
const result = detectOutboundFetcher(src);
|
|
34
|
+
expect(result.hasOutboundFetch).toBe(true);
|
|
35
|
+
expect(result.hasUserInfluencedUrl).toBe(false);
|
|
36
|
+
expect(result.isRisky).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it("does NOT flag when no fetch present", () => {
|
|
39
|
+
const src = `
|
|
40
|
+
export async function POST(request: Request) {
|
|
41
|
+
const body = await request.json();
|
|
42
|
+
await prisma.user.create({ data: body });
|
|
43
|
+
return Response.json({ ok: true });
|
|
44
|
+
}`;
|
|
45
|
+
const result = detectOutboundFetcher(src);
|
|
46
|
+
expect(result.hasOutboundFetch).toBe(false);
|
|
47
|
+
expect(result.isRisky).toBe(false);
|
|
48
|
+
expect(result.evidence).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
it("does NOT match fetchUser() as outbound fetch", () => {
|
|
51
|
+
const src = `
|
|
52
|
+
export async function GET(request: Request) {
|
|
53
|
+
const url = new URL(request.url);
|
|
54
|
+
const user = await fetchUser(url.searchParams.get("id"));
|
|
55
|
+
return Response.json(user);
|
|
56
|
+
}`;
|
|
57
|
+
const result = detectOutboundFetcher(src);
|
|
58
|
+
expect(result.hasOutboundFetch).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it("detects got() with user input", () => {
|
|
61
|
+
const src = `
|
|
62
|
+
import got from "got";
|
|
63
|
+
export async function GET(request: Request) {
|
|
64
|
+
const target = new URL(request.url).searchParams.get("url");
|
|
65
|
+
const response = await got(target);
|
|
66
|
+
return Response.json(response.body);
|
|
67
|
+
}`;
|
|
68
|
+
const result = detectOutboundFetcher(src);
|
|
69
|
+
expect(result.isRisky).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it("detects undici.request with user input", () => {
|
|
72
|
+
const src = `
|
|
73
|
+
import { request as undiciRequest } from "undici";
|
|
74
|
+
export async function POST(req: Request) {
|
|
75
|
+
const body = await req.json();
|
|
76
|
+
const { body: responseBody } = await undici.request(body.endpoint);
|
|
77
|
+
return Response.json(responseBody);
|
|
78
|
+
}`;
|
|
79
|
+
const result = detectOutboundFetcher(src);
|
|
80
|
+
expect(result.isRisky).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=outbound-fetch.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-fetch.test.js","sourceRoot":"","sources":["../../src/util/outbound-fetch.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fourteensystems/shipguard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Static analysis guardrail for Next.js SaaS — flags unprotected routes, missing rate limiting, and SSRF surfaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"shipguard": "./bin/shipguard.mjs"
|