Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  file-consent.test.ts

  Sprache: JAVA
 

import { describe, expect, it, vi } from "vitest";
import {
  CONSENT_UPLOAD_HOST_ALLOWLIST,
  isPrivateOrReservedIP,
  uploadToConsentUrl,
  validateConsentUploadUrl,
} from "./file-consent.js";

// Helper: a resolveFn that returns a public IP by default
const publicResolve = async () => ({ address: "13.107.136.10" });
// Helper: a resolveFn that returns a private IP
const privateResolve = (ip: string) => async () => ({ address: ip });
// Helper: a resolveFn that returns multiple addresses
const multiResolve = (ips: string[]) => async () => ips.map((address) => ({ address }));
// Helper: a resolveFn that fails
const failingResolve = async () => {
  throw new Error("DNS failure");
};

// ─── isPrivateOrReservedIP ───────────────────────────────────────────────────

describe("isPrivateOrReservedIP", () => {
  it.each([
    ["10.0.0.1"true],
    ["10.255.255.255"true],
    ["172.16.0.1"true],
    ["172.31.255.255"true],
    ["172.15.0.1"false],
    ["172.32.0.1"false],
    ["192.168.0.1"true],
    ["192.168.255.255"true],
    ["127.0.0.1"true],
    ["127.255.255.255"true],
    ["169.254.0.1"true],
    ["169.254.169.254"true],
    ["0.0.0.0"true],
    ["8.8.8.8"false],
    ["13.107.136.10"false],
    ["52.96.0.1"false],
  ] as const)("IPv4 %s → %s", (ip, expected) => {
    expect(isPrivateOrReservedIP(ip)).toBe(expected);
  });

  it.each([
    ["::1"true],
    ["::"true],
    ["fe80::1"true],
    ["fe80::"true],
    ["fc00::1"true],
    ["fd12:3456::1"true],
    ["2001:0db8::1"false],
    ["2620:1ec:c11::200"false],
    // IPv4-mapped IPv6 addresses
    ["::ffff:127.0.0.1"true],
    ["::ffff:10.0.0.1"true],
    ["::ffff:192.168.1.1"true],
    ["::ffff:169.254.169.254"true],
    ["::ffff:8.8.8.8"false],
    ["::ffff:13.107.136.10"false],
  ] as const)("IPv6 %s → %s", (ip, expected) => {
    expect(isPrivateOrReservedIP(ip)).toBe(expected);
  });

  it.each([
    ["999.999.999.999"false],
    ["256.0.0.1"false],
    ["10.0.0.256"false],
    ["-1.0.0.1"false],
    ["1.2.3.4.5"false],
  ] as const)("malformed IPv4 %s → %s", (ip, expected) => {
    expect(isPrivateOrReservedIP(ip)).toBe(expected);
  });
});

// ─── validateConsentUploadUrl ────────────────────────────────────────────────

describe("validateConsentUploadUrl", () => {
  it("accepts a valid SharePoint HTTPS URL", async () => {
    await expect(
      validateConsentUploadUrl("https://contoso.sharepoint.com/sites/uploads/file.pdf", {
        resolveFn: publicResolve,
      }),
    ).resolves.toBeUndefined();
  });

  it("accepts subdomains of allowlisted domains", async () => {
    await expect(
      validateConsentUploadUrl(
        "https://contoso-my.sharepoint.com/personal/user/Documents/file.docx",
        { resolveFn: publicResolve },
      ),
    ).resolves.toBeUndefined();
  });

  it("accepts graph.microsoft.com", async () => {
    await expect(
      validateConsentUploadUrl("https://graph.microsoft.com/v1.0/me/drive/items/123/content", {
        resolveFn: publicResolve,
      }),
    ).resolves.toBeUndefined();
  });

  it("rejects non-HTTPS URLs", async () => {
    await expect(
      validateConsentUploadUrl("http://contoso.sharepoint.com/file.pdf", {
        resolveFn: publicResolve,
      }),
    ).rejects.toThrow("must use HTTPS");
  });

  it("rejects invalid URLs", async () => {
    await expect(
      validateConsentUploadUrl("not a url", { resolveFn: publicResolve }),
    ).rejects.toThrow("not a valid URL");
  });

  it("rejects hosts not in the allowlist", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.example.com/exfil", { resolveFn: publicResolve }),
    ).rejects.toThrow("not in the allowed domains");
  });

  it("rejects an SSRF attempt with internal metadata URL", async () => {
    await expect(
      validateConsentUploadUrl("https://169.254.169.254/latest/meta-data/", {
        resolveFn: publicResolve,
      }),
    ).rejects.toThrow("not in the allowed domains");
  });

  it("rejects localhost", async () => {
    await expect(
      validateConsentUploadUrl("https://localhost:8080/internal", { resolveFn: publicResolve }),
    ).rejects.toThrow("not in the allowed domains");
  });

  it("rejects when DNS resolves to a private IPv4 (10.x)", async () => {
    await expect(
      validateConsentUploadUrl("https://malicious.sharepoint.com/exfil", {
        resolveFn: privateResolve("10.0.0.1"),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("rejects when DNS resolves to loopback", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: privateResolve("127.0.0.1"),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("rejects when DNS resolves to link-local (169.254.x.x)", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: privateResolve("169.254.169.254"),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("rejects when DNS resolves to IPv6 loopback", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: privateResolve("::1"),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("rejects when DNS resolves to IPv4-mapped IPv6 private address", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: privateResolve("::ffff:10.0.0.1"),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("rejects when DNS resolves to IPv4-mapped IPv6 loopback", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: privateResolve("::ffff:127.0.0.1"),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("rejects when any DNS answer is private/reserved", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: multiResolve(["13.107.136.10""10.0.0.1"]),
      }),
    ).rejects.toThrow("private/reserved IP");
  });

  it("accepts when all DNS answers are public", async () => {
    await expect(
      validateConsentUploadUrl("https://evil.sharepoint.com/path", {
        resolveFn: multiResolve(["13.107.136.10""52.96.0.1"]),
      }),
    ).resolves.toBeUndefined();
  });

  it("rejects when DNS resolution fails", async () => {
    await expect(
      validateConsentUploadUrl("https://nonexistent.sharepoint.com/path", {
        resolveFn: failingResolve,
      }),
    ).rejects.toThrow("Failed to resolve");
  });

  it("accepts a custom allowlist", async () => {
    await expect(
      validateConsentUploadUrl("https://custom.example.org/file", {
        allowlist: ["example.org"],
        resolveFn: publicResolve,
      }),
    ).resolves.toBeUndefined();
  });

  it("rejects hosts that are suffix-tricked (e.g. notsharepoint.com)", async () => {
    await expect(
      validateConsentUploadUrl("https://notsharepoint.com/file", { resolveFn: publicResolve }),
    ).rejects.toThrow("not in the allowed domains");
  });

  it("rejects file:// protocol", async () => {
    await expect(
      validateConsentUploadUrl("file:///etc/passwd", { resolveFn: publicResolve }),
    ).rejects.toThrow("must use HTTPS");
  });
});

// ─── CONSENT_UPLOAD_HOST_ALLOWLIST ───────────────────────────────────────────

describe("CONSENT_UPLOAD_HOST_ALLOWLIST", () => {
  it("contains only Microsoft/SharePoint domains", () => {
    for (const domain of CONSENT_UPLOAD_HOST_ALLOWLIST) {
      expect(
        domain.includes("microsoft") ||
          domain.includes("sharepoint") ||
          domain.includes("onedrive") ||
          domain.includes("1drv") ||
          domain.includes("live.com"),
      ).toBe(true);
    }
  });

  it("does not contain overly broad domains", () => {
    const broad = [
      "microsoft.com",
      "azure.com",
      "blob.core.windows.net",
      "azureedge.net",
      "trafficmanager.net",
    ];
    for (const domain of broad) {
      expect(CONSENT_UPLOAD_HOST_ALLOWLIST).not.toContain(domain);
    }
  });
});

// ─── uploadToConsentUrl (integration with validation) ────────────────────────

describe("uploadToConsentUrl", () => {
  it("sends the OpenClaw User-Agent header with consent uploads", async () => {
    const fetchFn = vi.fn(async () => new Response(null, { status: 200 }));

    await uploadToConsentUrl({
      url: "https://contoso.sharepoint.com/upload",
      buffer: Buffer.from("hello"),
      fetchFn,
      validationOpts: { resolveFn: publicResolve },
    });

    expect(fetchFn).toHaveBeenCalledWith(
      "https://contoso.sharepoint.com/upload",
      expect.objectContaining({
        method: "PUT",
        headers: expect.objectContaining({
          "Content-Range""bytes 0-4/5",
          "Content-Type""application/octet-stream",
          "User-Agent": expect.stringMatching(/^teams\.ts\[apps\]\/.+ OpenClaw\/.+$/),
        }),
      }),
    );
  });

  it("blocks upload to a disallowed host", async () => {
    const mockFetch = vi.fn();
    await expect(
      uploadToConsentUrl({
        url: "https://evil.example.com/exfil",
        buffer: Buffer.from("secret data"),
        fetchFn: mockFetch,
        validationOpts: { resolveFn: publicResolve },
      }),
    ).rejects.toThrow("not in the allowed domains");

    expect(mockFetch).not.toHaveBeenCalled();
  });

  it("blocks upload to a private IP", async () => {
    const mockFetch = vi.fn();
    await expect(
      uploadToConsentUrl({
        url: "https://compromised.sharepoint.com/upload",
        buffer: Buffer.from("data"),
        fetchFn: mockFetch,
        validationOpts: { resolveFn: privateResolve("10.0.0.1") },
      }),
    ).rejects.toThrow("private/reserved IP");

    expect(mockFetch).not.toHaveBeenCalled();
  });

  it("allows upload to a valid SharePoint URL and performs PUT", async () => {
    const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
    const buffer = Buffer.from("file content");

    await uploadToConsentUrl({
      url: "https://contoso.sharepoint.com/sites/uploads/file.pdf",
      buffer,
      contentType: "application/pdf",
      fetchFn: mockFetch,
      validationOpts: { resolveFn: publicResolve },
    });

    expect(mockFetch).toHaveBeenCalledOnce();
    const [url, opts] = mockFetch.mock.calls[0];
    expect(url).toBe("https://contoso.sharepoint.com/sites/uploads/file.pdf");
    expect(opts.method).toBe("PUT");
    expect(opts.headers["Content-Type"]).toBe("application/pdf");
  });

  it("throws on non-OK response after passing validation", async () => {
    const mockFetch = vi.fn().mockResolvedValue({
      ok: false,
      status: 403,
      statusText: "Forbidden",
    });

    await expect(
      uploadToConsentUrl({
        url: "https://contoso.sharepoint.com/sites/uploads/file.pdf",
        buffer: Buffer.from("data"),
        fetchFn: mockFetch,
        validationOpts: { resolveFn: publicResolve },
      }),
    ).rejects.toThrow("File upload to consent URL failed: 403 Forbidden");
  });

  it("blocks HTTP (non-HTTPS) upload before fetch is called", async () => {
    const mockFetch = vi.fn();
    await expect(
      uploadToConsentUrl({
        url: "http://contoso.sharepoint.com/upload",
        buffer: Buffer.from("data"),
        fetchFn: mockFetch,
        validationOpts: { resolveFn: publicResolve },
      }),
    ).rejects.toThrow("must use HTTPS");

    expect(mockFetch).not.toHaveBeenCalled();
  });
});

Messung V0.5 in Prozent
C=97 H=96 G=96

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet am  2026-05-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge