VYPR
High severity8.7NVD Advisory· Published May 11, 2026· Updated May 13, 2026

CVE-2026-43897

CVE-2026-43897

Description

Link Preview JS extracts web links information. Prior to 4.0.1, the library did not check for IPv6 loopback attacks. There was also a DNS attack, where an address could be resolved into an internal IP. This could cause internal data leaks. This vulnerability is fixed in 4.0.1.

Patches

1
4396d48909fa

Merge pull request #179 from OP-Engineering/oscar/loopback-fixes

4 files changed · +72 3
  • constants.ts+17 2 modified
    @@ -5,6 +5,9 @@ export const CONSTANTS = {
           "(?:(?:https?|ftp)://)" +
           // user:pass authentication
           "(?:\\S+(?::\\S*)?@)?" +
    +      // block internal-only hostnames and wildcard DNS rebinding services
    +      "(?![^/?#]+\\.(?:internal|local)(?::\\d{2,5})?(?:[/?#]|$))" +
    +      "(?![^/?#]+\\.(?:nip|sslip)\\.io(?::\\d{2,5})?(?:[/?#]|$))" +
           "(?:" +
           // IP address exclusion
           // private & local networks
    @@ -34,7 +37,7 @@ export const CONSTANTS = {
           // resource path
           "(?:[/?#]\\S*)?" +
           "$",
    -    "i"
    +    "i",
       ),
     
       REGEX_LOOPBACK: new RegExp(
    @@ -63,8 +66,20 @@ export const CONSTANTS = {
           "|" +
           // Carrier-Grade NAT (CGNAT): 100.64.0.0 - 100.127.255.255
           "(?:100\\.(?:6[4-9]|[7-9]\\d|1[0-1]\\d)(?:\\.\\d{1,3}){2})" +
    +      "|" +
    +      // IPv6 loopback
    +      "(?:::1)" +
    +      "|" +
    +      // IPv4-mapped IPv6 loopback: ::ffff:127.0.0.0/104
    +      "(?:::ffff:127(?:\\.\\d{1,3}){3})" +
    +      "|" +
    +      // IPv6 Unique Local Address (ULA): fc00::/7
    +      "(?:f[c-d][0-9a-f]{2}:[0-9a-f:]+)" +
    +      "|" +
    +      // IPv6 link-local unicast: fe80::/10
    +      "(?:fe[89ab][0-9a-f]:[0-9a-f:]+)" +
           "$",
    -    "i"
    +    "i",
       ),
     
       REGEX_CONTENT_TYPE_IMAGE: new RegExp("image/.*", "i"),
    
  • index.ts+4 0 modified
    @@ -413,6 +413,10 @@ export async function getLinkPreview(text: string, options?: ILinkPreviewOptions
         const resolvedUrl = await options.resolveDNSHost(detectedUrl);
     
         throwOnLoopback(resolvedUrl);
    +  } else {
    +    console.error(
    +      "[link-preview-js] You are not resolving DNS addresses (resolveDNSHost option) before fetching a link. This can cause loopback attacks. Always try to resolve DNS addresses",
    +    );
       }
     
       const timeout = options?.timeout ?? 3000; // 3 second timeout default
    
  • __tests__/index.spec.ts+42 0 modified
    @@ -1,6 +1,22 @@
     import { getLinkPreview, getPreviewFromContent } from "../index";
    +import { CONSTANTS } from "../constants";
     import prefetchedResponse from "./sampleResponse.json";
     
    +describe(`#REGEX_LOOPBACK`, () => {
    +  it(`matches IPv6 loopback and local ranges`, () => {
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`::1`)).toBe(true);
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`::ffff:127.0.0.1`)).toBe(true);
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`fc00::1`)).toBe(true);
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`fd12:3456:789a::1`)).toBe(true);
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`fe80::abcd`)).toBe(true);
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`febf::abcd`)).toBe(true);
    +  });
    +
    +  it(`does not match non-local IPv6 addresses`, () => {
    +    expect(CONSTANTS.REGEX_LOOPBACK.test(`2001:4860:4860::8888`)).toBe(false);
    +  });
    +});
    +
     describe(`#getLinkPreview()`, () => {
       it(`should extract link info from just URL`, async () => {
         const linkInfo: any = await getLinkPreview(`https://www.youtube.com/watch?v=wuClZjOdT30`, {
    @@ -150,6 +166,32 @@ describe(`#getLinkPreview()`, () => {
         ).rejects.toThrowErrorMatchingSnapshot();
       });
     
    +  it(`should block .internal hostnames`, async () => {
    +    await expect(
    +      getLinkPreview(
    +        `http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token`,
    +      ),
    +    ).rejects.toThrowErrorMatchingSnapshot();
    +  });
    +
    +  it(`should block .local hostnames`, async () => {
    +    await expect(
    +      getLinkPreview(`http://printer.local/status`),
    +    ).rejects.toThrowErrorMatchingSnapshot();
    +  });
    +
    +  it(`should block nip.io wildcard hostnames`, async () => {
    +    await expect(
    +      getLinkPreview(`http://169.254.169.254.nip.io/latest/meta-data/iam/security-credentials/`),
    +    ).rejects.toThrowErrorMatchingSnapshot();
    +  });
    +
    +  it(`should block sslip.io wildcard hostnames`, async () => {
    +    await expect(
    +      getLinkPreview(`http://127.0.0.1.sslip.io/`),
    +    ).rejects.toThrowErrorMatchingSnapshot();
    +  });
    +
       it(`should handle empty strings gracefully`, async () => {
         await expect(getLinkPreview(``)).rejects.toThrowErrorMatchingSnapshot();
       });
    
  • __tests__/__snapshots__/index.spec.ts.snap+9 1 modified
    @@ -1,7 +1,15 @@
    -// Jest Snapshot v1, https://goo.gl/fbAQLP
    +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
     
     exports[`#getLinkPreview() no link in text should fail gracefully 1`] = `"link-preview-js did not receive a valid a url or text"`;
     
    +exports[`#getLinkPreview() should block .internal hostnames 1`] = `"link-preview-js did not receive a valid a url or text"`;
    +
    +exports[`#getLinkPreview() should block .local hostnames 1`] = `"link-preview-js did not receive a valid a url or text"`;
    +
    +exports[`#getLinkPreview() should block nip.io wildcard hostnames 1`] = `"link-preview-js did not receive a valid a url or text"`;
    +
    +exports[`#getLinkPreview() should block sslip.io wildcard hostnames 1`] = `"link-preview-js did not receive a valid a url or text"`;
    +
     exports[`#getLinkPreview() should handle empty strings gracefully 1`] = `"link-preview-js did not receive a valid url or text"`;
     
     exports[`#getLinkPreview() should handle malformed urls gracefully 1`] = `"link-preview-js did not receive a valid a url or text"`;
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.