Eine aufbereitete Darstellung der Quelle

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

Benutzer

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=94 H=95 G=94

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

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

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

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik