Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ This package exports two methods:

* verifyCredential
* verifyPresentation
* parseAndValidateUrl
* checkUrlSafety

### URL utilities

`parseAndValidateUrl(value, options)` normalizes and validates URLs for use inside LCW or Verifier Plus. It enforces `http/https` schemes by default, optional TLD requirements, rejects private/localhost hosts, and lets you provide block/allow lists so a single rule-set can be shared across applications.

`checkUrlSafety(value, options)` builds on the validator and lets you plug in blocklists or external reputation matchers (for example Google Safe Browsing). The helper returns `{ status: 'safe' | 'suspicious' | 'blocked', reasons: [...] }` so callers can disable links or show warnings while reusing the same policy surface in multiple repos.

### verifyCredential

Expand Down
21 changes: 21 additions & 0 deletions src/constants/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*!
* Copyright (c) 2025 Digital Credentials Consortium.
* All rights reserved.
*/

export const DEFAULT_ALLOWED_SCHEMES = ['http', 'https'] as const;
export const DEFAULT_MAX_URL_LENGTH = 2048;

export const RESERVED_HOST_SUFFIXES = [
'.localhost',
'.local',
'.internal',
'.home',
'.invalid',
'.test',
'.example'
];

export const RESERVED_HOSTNAMES = ['localhost'];


17 changes: 15 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
/*!
* Copyright (c) 2022 Digital Credentials Consortium. All rights reserved.
*/
export { verifyCredential, verifyPresentation
} from './Verify.js'
export { verifyCredential, verifyPresentation } from './Verify.js';
export {
parseAndValidateUrl,
checkUrlSafety
} from './urlUtils.js';
export type {
UrlSafetyMatcher,
UrlSafetyMatcherResult,
UrlSafetyOptions,
UrlSafetyResult,
UrlSafetyStatus,
UrlValidationOptions,
UrlValidationReason,
UrlValidationResult
} from './types/url.js';
59 changes: 59 additions & 0 deletions src/test-fixtures/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*!
* Copyright (c) 2025 Digital Credentials Consortium.
* All rights reserved.
*/

import type { UrlValidationOptions } from '../types/url.js';

export type ValidUrlFixture = {
value: string;
hostname?: string;
options?: UrlValidationOptions;
};

export type InvalidUrlFixture = {
value: string;
reason: string;
options?: UrlValidationOptions;
};

export const validUrlFixtures: ValidUrlFixture[] = [
{
value: 'https://example.org/path?foo=bar#hash',
hostname: 'example.org'
},
{
value: 'http://sub.example.com/resource',
hostname: 'sub.example.com'
},
{
value: 'https://10.0.0.5/data',
hostname: '10.0.0.5',
options: { allowPrivateHosts: true, requireTld: false }
}
];

export const invalidUrlFixtures: InvalidUrlFixture[] = [
{
value: '',
reason: 'empty'
},
{
value: 'ftp://example.org',
reason: 'disallowed_scheme'
},
{
value: 'https://192.168.1.20',
reason: 'private_host'
},
{
value: 'https://intranet',
reason: 'missing_tld'
},
{
value: 'https://danger.example.com',
reason: 'blocked_host',
options: { blockedHosts: ['danger.example.com'] }
}
];

54 changes: 54 additions & 0 deletions src/types/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*!
* Copyright (c) 2025 Digital Credentials Consortium.
* All rights reserved.
*/

export type UrlValidationReason =
| 'empty'
| 'too_long'
| 'invalid_format'
| 'disallowed_scheme'
| 'missing_hostname'
| 'missing_tld'
| 'private_host'
| 'blocked_host';

export interface UrlValidationOptions {
allowedSchemes?: string[];
maxLength?: number;
requireTld?: boolean;
allowPrivateHosts?: boolean;
blockedHosts?: string[];
allowedHosts?: string[];
}

export interface UrlValidationResult {
ok: boolean;
normalizedUrl?: string;
hostname?: string;
reason?: UrlValidationReason;
}

export type UrlSafetyStatus = 'safe' | 'suspicious' | 'blocked';

export interface UrlSafetyResult {
url: string;
status: UrlSafetyStatus;
reasons: string[];
}

export type UrlSafetyMatcher =
| ((url: URL) => Promise<UrlSafetyMatcherResult | null>)
| ((url: URL) => UrlSafetyMatcherResult | null);

export interface UrlSafetyMatcherResult {
status: UrlSafetyStatus;
reason: string;
}

export interface UrlSafetyOptions {
blocklist?: Iterable<string>;
allowlist?: Iterable<string>;
matchers?: UrlSafetyMatcher[];
}

Loading