🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@promptshield/core

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@promptshield/core - npm Package Compare versions

Comparing version
0.1.0
to
1.0.0
+271
-74
dist/index.d.mts

@@ -82,3 +82,3 @@ /**

*/
interface ThreatLoc {
interface Location {
/** 1-based line number */

@@ -98,3 +98,3 @@ line: number;

*/
interface ThreatReport {
interface ThreatReportWithoutLocation {
/**

@@ -119,3 +119,8 @@ * Stable rule identifier.

/** Location of the threat start */
loc: ThreatLoc;
range: {
/** Offset: 0-based character index */
start: number;
/** Offset: 0-based character index */
end: number;
};
/**

@@ -155,9 +160,63 @@ * The substring responsible for the detection.

referenceUrl: string;
/**
* Indicates whether this threat was suppressed
* by an ignore directive.
*/
suppressed?: boolean;
}
/**
* Threat report enriched with human-readable location information.
*
* `ThreatReport` extends the base `ThreatReportWithoutLocation` by replacing the
* offset-based `range` with resolved line/column locations. This format is
* intended for environments where diagnostics must be presented to humans,
* such as:
*
* - CLI output
* - CI reports
* - logs
* - editor diagnostics
*
* The core scanner operates purely on absolute character offsets for
* performance and interoperability with editor APIs (e.g., Tiptap, LSP).
* Location resolution is performed later using utilities such as
* `enrichWithLoc`.
*
* Each range endpoint includes:
*
* - `line` — 1-based line number
* - `column` — 1-based column number
* - `index` — original 0-based character offset
*
* Keeping the original `index` ensures deterministic mapping back to the
* source text while still providing user-friendly diagnostics.
*
* @example
* ```ts
* {
* ruleId: "PSU001",
* severity: "LOW",
* message: "Invisible Unicode characters detected.",
* range: {
* start: { line: 2, column: 5, index: 17 },
* end: { line: 2, column: 6, index: 18 }
* }
* }
* ```
*/
interface ThreatReport extends Omit<ThreatReportWithoutLocation, "range"> {
range: {
/** Start position of the detected threat span. */
start: Location;
/** End position of the detected threat span. */
end: Location;
};
}
/**
* Predicate used by the scanner to determine whether a detected
* threat span should be ignored.
*
* @param start - Absolute 0-based character index of the threat start (inclusive).
* @param end - Absolute 0-based character index of the threat end (exclusive).
*
* The predicate should return `true` only if the **entire span**
* falls within an ignore range.
*/
type IgnoreChecker = (start: number, end: number) => boolean;
/**
* Scanner configuration options.

@@ -210,2 +269,8 @@ */

disableInjectionPatterns?: boolean;
/**
* Ignore checker function.
*
* @default () => false
*/
ignoreChecker?: IgnoreChecker;
}

@@ -231,3 +296,3 @@ /**

*/
lineOffsets?: number[];
lineOffsets: number[];
}

@@ -242,11 +307,4 @@ /**

*/
type Detector = (text: string, options: ScanOptions, context: ScanContext) => ThreatReport[];
type Detector = (text: string, options: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Performance metrics for a scan.
*/
interface ScanStats {
durationMs: number;
totalChars: number;
}
/**
* Result returned by the scanner.

@@ -256,3 +314,2 @@ */

threats: ThreatReport[];
stats: ScanStats;
isClean: boolean;

@@ -262,2 +319,25 @@ }

/**
* Core scanning entry point.
*
* Executes all enabled detectors in priority order:
*
* 1. Trojan Source (BIDI logic manipulation)
* 2. Invisible characters
* 3. Homoglyph spoofing
* 4. Unicode normalization anomalies
* 5. Smuggling techniques
*
* @example
* ```ts
* import { scan } from '@promptshield/core';
*
* const result = scan("Hello\u200BWorld");
* if (!result.isClean) {
* console.log(result.threats);
* }
* ```
*/
declare const scan: (text: string, options?: ScanOptions) => ScanResult;
/**
* Homoglyph detector.

@@ -292,21 +372,45 @@ *

*/
declare const scanHomoglyphs: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanHomoglyphs: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Scan for deterministic prompt-injection patterns.
* Scan text for deterministic prompt-injection patterns.
*
* Detection strategy:
* - Scan line-by-line for stable location reporting
* - Attempt direct regex detection first
* - Fall back to normalized detection
*
* Span semantics:
* offendingText = matched instruction phrase or entire line
* 1. Perform **direct regex matching** against the raw text.
* 2. Perform **normalized matching** to catch obfuscation such as:
*
* - excessive whitespace
* - character splitting
* - accent obfuscation
*
* Example:
*
* i g n o r e
* previous
* instructions
*
* To avoid duplicate reporting:
*
* - Direct matches are recorded first.
* - Normalized matches are skipped if they overlap an already
* detected span for the same rule.
*
* Complexity:
*
* - One normalization pass over the text
* - One regex scan per rule
* - One incremental normalized search per rule
*
* Overall runtime remains **linear in input size**.
*
* @param text Raw text to scan
* @param options Scanner configuration
*/
declare const scanInjectionPatterns: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanInjectionPatterns: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Invisible-character detector.
* Invisible character detector.
*
* Emits one primary span-level rule using precedence:
* This detector emits **span-level threats** with the following precedence:
*

@@ -317,5 +421,13 @@ * PSU004 → Unicode tag payload

*
* PSU002 is emitted independently for boundary manipulation.
* Additionally:
*
* PSU002 is emitted independently for **token boundary manipulation**
* where an invisible character appears inside a visible token.
*
* Span semantics:
*
* • offendingText represents the **entire invisible sequence**
* • spans are **not merged across newline boundaries**
*/
declare const scanInvisibleChars: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanInvisibleChars: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**

@@ -330,6 +442,6 @@ * Attempts to decode Unicode tag characters into ASCII text.

*
* ASCII = codePoint - 0xE0000
* ASCII = codePoint − 0xE0000
*
* Attackers can use this mechanism to embed hidden instructions
* or metadata inside otherwise invisible text streams.
* This mechanism has been abused in multiple security reports to embed
* hidden instructions inside invisible character streams.
*

@@ -343,31 +455,42 @@ * This decoder performs a best-effort extraction.

*
* Detects characters that change under NFKC normalization.
* Detects characters whose representation changes under **NFKC normalization**.
*
* Unicode normalization can transform visually similar characters
* into canonical equivalents. When user-visible text differs from
* its normalized form, this may indicate:
* Unicode normalization may transform visually similar or compatibility
* characters into canonical equivalents. When displayed text differs from
* its normalized form, this can introduce ambiguity between what users see
* and what downstream systems interpret.
*
* Such situations may indicate:
*
* - compatibility glyph usage
* - spoofing attempts
* - homoglyph confusion
* - prompt-smuggling techniques
* - content-validation bypass
* - prompt smuggling techniques
* - validation bypass in downstream processing pipelines
*
* Detection model:
* - Compare each character against its NFKC-normalized form
* - Group adjacent normalization-sensitive characters into a span
* - Emit one threat per span
*
* NOTE:
* This is intentionally heuristic. Many normalization changes are
* legitimate in multilingual text, but normalization-sensitive spans
* in prompts or code should be surfaced for inspection.
* 1. Normalize the text using **NFKC**
* 2. Iterate over characters in the original text
* 3. Identify characters whose normalized form differs
* 4. Group adjacent normalization-sensitive characters into spans
* 5. Emit one threat per span
*
* Severity heuristic:
*
* - **PSN001 (LOW)**
* Compatibility normalization producing simple ASCII text.
*
* - **PSN002 (MEDIUM)**
* More complex normalization transformations.
*
* Span semantics:
* offendingText = original span
* decodedPayload = normalized span
*
* Rule:
* PSN001 — Normalization-sensitive text detected
* offendingText = original span
* decodedPayload = normalized span
*
* Normalization can expand characters (example: `ff → ff`), therefore
* the normalized payload is computed from the entire span.
*/
declare const scanNormalization: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanNormalization: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];

@@ -379,3 +502,4 @@ /** biome-ignore-all lint/suspicious/noAssignInExpressions: iterating over regex matches */

*
* Detects techniques used to conceal instructions or data inside text.
* Detects techniques used to conceal instructions or payloads
* inside otherwise harmless-looking text.
*

@@ -386,12 +510,13 @@ * Rules emitted:

* PSS002 — Base64 payload with readable content (MEDIUM)
* PSS006 — Hex-encoded payload with readable content (MEDIUM)
* PSS003 — Hidden Markdown comment (LOW)
* PSS004 — Invisible Markdown link (LOW)
* PSS005 — Hidden HTML container (LOW)
*
* Span semantics:
* offendingText = entire suspicious region
* decodedPayload = recovered payload when available
*
* Context is intentionally mutable so detectors can share `lineOffsets`.
* offendingText = suspicious region
* decodedPayload = recovered payload when available
*/
declare const scanSmuggling: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanSmuggling: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];

@@ -402,32 +527,104 @@ /**

* Detects unsafe usage of Unicode Bidirectional (BIDI) control characters
* that may cause visual ordering to differ from logical ordering.
* that can cause the *visual order* of text to differ from its *logical order*.
*
* These attacks allow malicious code or instructions to appear benign to
* reviewers while executing differently when interpreted by compilers,
* interpreters, or LLMs.
*
* Detection rules:
*
* PST001 — Matched BIDI override sequence
* PST002 — Unterminated BIDI override sequence
*/
declare const scanTrojanSource: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanTrojanSource: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Core scanning entry point.
* Computes line start offsets for a string.
*
* Executes all enabled detectors in priority order:
* Each entry represents the character index where a new line begins.
* The first entry is always `0`.
*
* 1. Trojan Source (BIDI logic manipulation)
* 2. Invisible characters
* 3. Homoglyph spoofing
* 4. Unicode normalization anomalies
* 5. Smuggling techniques
* Example:
* "a\nb\nc" → [0, 2, 4]
*
* The provided `context` object is shared across detectors and may be
* mutated for performance optimizations (e.g., caching line offsets).
* This enables fast index → (line, column) mapping without repeatedly
* scanning the entire string.
*/
declare const getLineOffsets: (text: string) => number[];
/**
* Resolves a character index into a line/column location.
*
* Uses binary search over precomputed line offsets for O(log n) lookup.
*
* Context provides:
* - baseLine
* - baseCol
* - lineOffsets
*
* `baseLine` and `baseCol` allow this function to operate correctly when
* scanning substrings that originate from a larger document.
*/
declare const getLocForIndex: (index: number, context: Required<ScanContext>) => {
line: number;
column: number;
index: number;
};
/**
* Enriches ThreatReports with human-readable line/column locations.
*
* PromptShield detectors operate on absolute character offsets for
* performance and editor compatibility (e.g., Tiptap, LSP, AST tools).
*
* However, human-facing environments such as:
*
* - CLI output
* - CI diagnostics
* - logs
* - static analysis reports
*
* require line and column information.
*
* This helper converts offset-based threat ranges into location-aware
* structures in a single pass.
*
* The function computes line offsets once and resolves both start and
* end positions using binary search (`getLocForIndex`).
*
* This approach is significantly more efficient than resolving locations
* during detection or performing repeated scans of the input text.
*
* @param threats
* List of ThreatReports produced by `scan()`. These must contain
* offset-based ranges (`range.start`, `range.end`).
*
* @param text
* The original scanned text used to generate the threats.
*
* @param context
* Optional scan context for offset translation. Supports:
* - `baseLine`
* - `baseCol`
*
* This is useful when scanning substrings embedded inside a larger
* document (e.g., editor buffers, LSP fragments).
*
* @returns
* A new array of ThreatReports where each threat includes
* `loc.start` and `loc.end` describing the resolved line/column
* positions.
*
* @example
* ```ts
* import { scan } from '@promptshield/core';
* const result = scan(text);
* const threats = enrichWithLoc(result.threats, text);
*
* const result = scan("Hello\u200BWorld");
* if (!result.isClean) {
* console.log(result.threats);
* }
* console.log(threats[0].loc);
* // {
* // start: { line: 2, column: 5, index: 17 },
* // end: { line: 2, column: 8, index: 20 }
* // }
* ```
*/
declare const scan: (text: string, options?: ScanOptions, context?: ScanContext) => ScanResult;
declare const enrichWithLocation: (threats: ThreatReportWithoutLocation[], text: string, context?: Omit<ScanContext, "lineOffsets">) => ThreatReport[];
export { type Detector, SEVERITY_MAP, type ScanContext, type ScanOptions, type ScanResult, type ScanStats, type Severity, ThreatCategory, type ThreatLoc, type ThreatReport, decodeUnicodeTags, scan, scanHomoglyphs, scanInjectionPatterns, scanInvisibleChars, scanNormalization, scanSmuggling, scanTrojanSource };
export { type Detector, type IgnoreChecker, type Location, SEVERITY_MAP, type ScanContext, type ScanOptions, type ScanResult, type Severity, ThreatCategory, type ThreatReport, type ThreatReportWithoutLocation, decodeUnicodeTags, enrichWithLocation, getLineOffsets, getLocForIndex, scan, scanHomoglyphs, scanInjectionPatterns, scanInvisibleChars, scanNormalization, scanSmuggling, scanTrojanSource };

@@ -82,3 +82,3 @@ /**

*/
interface ThreatLoc {
interface Location {
/** 1-based line number */

@@ -98,3 +98,3 @@ line: number;

*/
interface ThreatReport {
interface ThreatReportWithoutLocation {
/**

@@ -119,3 +119,8 @@ * Stable rule identifier.

/** Location of the threat start */
loc: ThreatLoc;
range: {
/** Offset: 0-based character index */
start: number;
/** Offset: 0-based character index */
end: number;
};
/**

@@ -155,9 +160,63 @@ * The substring responsible for the detection.

referenceUrl: string;
/**
* Indicates whether this threat was suppressed
* by an ignore directive.
*/
suppressed?: boolean;
}
/**
* Threat report enriched with human-readable location information.
*
* `ThreatReport` extends the base `ThreatReportWithoutLocation` by replacing the
* offset-based `range` with resolved line/column locations. This format is
* intended for environments where diagnostics must be presented to humans,
* such as:
*
* - CLI output
* - CI reports
* - logs
* - editor diagnostics
*
* The core scanner operates purely on absolute character offsets for
* performance and interoperability with editor APIs (e.g., Tiptap, LSP).
* Location resolution is performed later using utilities such as
* `enrichWithLoc`.
*
* Each range endpoint includes:
*
* - `line` — 1-based line number
* - `column` — 1-based column number
* - `index` — original 0-based character offset
*
* Keeping the original `index` ensures deterministic mapping back to the
* source text while still providing user-friendly diagnostics.
*
* @example
* ```ts
* {
* ruleId: "PSU001",
* severity: "LOW",
* message: "Invisible Unicode characters detected.",
* range: {
* start: { line: 2, column: 5, index: 17 },
* end: { line: 2, column: 6, index: 18 }
* }
* }
* ```
*/
interface ThreatReport extends Omit<ThreatReportWithoutLocation, "range"> {
range: {
/** Start position of the detected threat span. */
start: Location;
/** End position of the detected threat span. */
end: Location;
};
}
/**
* Predicate used by the scanner to determine whether a detected
* threat span should be ignored.
*
* @param start - Absolute 0-based character index of the threat start (inclusive).
* @param end - Absolute 0-based character index of the threat end (exclusive).
*
* The predicate should return `true` only if the **entire span**
* falls within an ignore range.
*/
type IgnoreChecker = (start: number, end: number) => boolean;
/**
* Scanner configuration options.

@@ -210,2 +269,8 @@ */

disableInjectionPatterns?: boolean;
/**
* Ignore checker function.
*
* @default () => false
*/
ignoreChecker?: IgnoreChecker;
}

@@ -231,3 +296,3 @@ /**

*/
lineOffsets?: number[];
lineOffsets: number[];
}

@@ -242,11 +307,4 @@ /**

*/
type Detector = (text: string, options: ScanOptions, context: ScanContext) => ThreatReport[];
type Detector = (text: string, options: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Performance metrics for a scan.
*/
interface ScanStats {
durationMs: number;
totalChars: number;
}
/**
* Result returned by the scanner.

@@ -256,3 +314,2 @@ */

threats: ThreatReport[];
stats: ScanStats;
isClean: boolean;

@@ -262,2 +319,25 @@ }

/**
* Core scanning entry point.
*
* Executes all enabled detectors in priority order:
*
* 1. Trojan Source (BIDI logic manipulation)
* 2. Invisible characters
* 3. Homoglyph spoofing
* 4. Unicode normalization anomalies
* 5. Smuggling techniques
*
* @example
* ```ts
* import { scan } from '@promptshield/core';
*
* const result = scan("Hello\u200BWorld");
* if (!result.isClean) {
* console.log(result.threats);
* }
* ```
*/
declare const scan: (text: string, options?: ScanOptions) => ScanResult;
/**
* Homoglyph detector.

@@ -292,21 +372,45 @@ *

*/
declare const scanHomoglyphs: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanHomoglyphs: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Scan for deterministic prompt-injection patterns.
* Scan text for deterministic prompt-injection patterns.
*
* Detection strategy:
* - Scan line-by-line for stable location reporting
* - Attempt direct regex detection first
* - Fall back to normalized detection
*
* Span semantics:
* offendingText = matched instruction phrase or entire line
* 1. Perform **direct regex matching** against the raw text.
* 2. Perform **normalized matching** to catch obfuscation such as:
*
* - excessive whitespace
* - character splitting
* - accent obfuscation
*
* Example:
*
* i g n o r e
* previous
* instructions
*
* To avoid duplicate reporting:
*
* - Direct matches are recorded first.
* - Normalized matches are skipped if they overlap an already
* detected span for the same rule.
*
* Complexity:
*
* - One normalization pass over the text
* - One regex scan per rule
* - One incremental normalized search per rule
*
* Overall runtime remains **linear in input size**.
*
* @param text Raw text to scan
* @param options Scanner configuration
*/
declare const scanInjectionPatterns: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanInjectionPatterns: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Invisible-character detector.
* Invisible character detector.
*
* Emits one primary span-level rule using precedence:
* This detector emits **span-level threats** with the following precedence:
*

@@ -317,5 +421,13 @@ * PSU004 → Unicode tag payload

*
* PSU002 is emitted independently for boundary manipulation.
* Additionally:
*
* PSU002 is emitted independently for **token boundary manipulation**
* where an invisible character appears inside a visible token.
*
* Span semantics:
*
* • offendingText represents the **entire invisible sequence**
* • spans are **not merged across newline boundaries**
*/
declare const scanInvisibleChars: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanInvisibleChars: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**

@@ -330,6 +442,6 @@ * Attempts to decode Unicode tag characters into ASCII text.

*
* ASCII = codePoint - 0xE0000
* ASCII = codePoint − 0xE0000
*
* Attackers can use this mechanism to embed hidden instructions
* or metadata inside otherwise invisible text streams.
* This mechanism has been abused in multiple security reports to embed
* hidden instructions inside invisible character streams.
*

@@ -343,31 +455,42 @@ * This decoder performs a best-effort extraction.

*
* Detects characters that change under NFKC normalization.
* Detects characters whose representation changes under **NFKC normalization**.
*
* Unicode normalization can transform visually similar characters
* into canonical equivalents. When user-visible text differs from
* its normalized form, this may indicate:
* Unicode normalization may transform visually similar or compatibility
* characters into canonical equivalents. When displayed text differs from
* its normalized form, this can introduce ambiguity between what users see
* and what downstream systems interpret.
*
* Such situations may indicate:
*
* - compatibility glyph usage
* - spoofing attempts
* - homoglyph confusion
* - prompt-smuggling techniques
* - content-validation bypass
* - prompt smuggling techniques
* - validation bypass in downstream processing pipelines
*
* Detection model:
* - Compare each character against its NFKC-normalized form
* - Group adjacent normalization-sensitive characters into a span
* - Emit one threat per span
*
* NOTE:
* This is intentionally heuristic. Many normalization changes are
* legitimate in multilingual text, but normalization-sensitive spans
* in prompts or code should be surfaced for inspection.
* 1. Normalize the text using **NFKC**
* 2. Iterate over characters in the original text
* 3. Identify characters whose normalized form differs
* 4. Group adjacent normalization-sensitive characters into spans
* 5. Emit one threat per span
*
* Severity heuristic:
*
* - **PSN001 (LOW)**
* Compatibility normalization producing simple ASCII text.
*
* - **PSN002 (MEDIUM)**
* More complex normalization transformations.
*
* Span semantics:
* offendingText = original span
* decodedPayload = normalized span
*
* Rule:
* PSN001 — Normalization-sensitive text detected
* offendingText = original span
* decodedPayload = normalized span
*
* Normalization can expand characters (example: `ff → ff`), therefore
* the normalized payload is computed from the entire span.
*/
declare const scanNormalization: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanNormalization: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];

@@ -379,3 +502,4 @@ /** biome-ignore-all lint/suspicious/noAssignInExpressions: iterating over regex matches */

*
* Detects techniques used to conceal instructions or data inside text.
* Detects techniques used to conceal instructions or payloads
* inside otherwise harmless-looking text.
*

@@ -386,12 +510,13 @@ * Rules emitted:

* PSS002 — Base64 payload with readable content (MEDIUM)
* PSS006 — Hex-encoded payload with readable content (MEDIUM)
* PSS003 — Hidden Markdown comment (LOW)
* PSS004 — Invisible Markdown link (LOW)
* PSS005 — Hidden HTML container (LOW)
*
* Span semantics:
* offendingText = entire suspicious region
* decodedPayload = recovered payload when available
*
* Context is intentionally mutable so detectors can share `lineOffsets`.
* offendingText = suspicious region
* decodedPayload = recovered payload when available
*/
declare const scanSmuggling: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanSmuggling: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];

@@ -402,32 +527,104 @@ /**

* Detects unsafe usage of Unicode Bidirectional (BIDI) control characters
* that may cause visual ordering to differ from logical ordering.
* that can cause the *visual order* of text to differ from its *logical order*.
*
* These attacks allow malicious code or instructions to appear benign to
* reviewers while executing differently when interpreted by compilers,
* interpreters, or LLMs.
*
* Detection rules:
*
* PST001 — Matched BIDI override sequence
* PST002 — Unterminated BIDI override sequence
*/
declare const scanTrojanSource: (text: string, options?: ScanOptions, context?: ScanContext) => ThreatReport[];
declare const scanTrojanSource: (text: string, options?: ScanOptions) => ThreatReportWithoutLocation[];
/**
* Core scanning entry point.
* Computes line start offsets for a string.
*
* Executes all enabled detectors in priority order:
* Each entry represents the character index where a new line begins.
* The first entry is always `0`.
*
* 1. Trojan Source (BIDI logic manipulation)
* 2. Invisible characters
* 3. Homoglyph spoofing
* 4. Unicode normalization anomalies
* 5. Smuggling techniques
* Example:
* "a\nb\nc" → [0, 2, 4]
*
* The provided `context` object is shared across detectors and may be
* mutated for performance optimizations (e.g., caching line offsets).
* This enables fast index → (line, column) mapping without repeatedly
* scanning the entire string.
*/
declare const getLineOffsets: (text: string) => number[];
/**
* Resolves a character index into a line/column location.
*
* Uses binary search over precomputed line offsets for O(log n) lookup.
*
* Context provides:
* - baseLine
* - baseCol
* - lineOffsets
*
* `baseLine` and `baseCol` allow this function to operate correctly when
* scanning substrings that originate from a larger document.
*/
declare const getLocForIndex: (index: number, context: Required<ScanContext>) => {
line: number;
column: number;
index: number;
};
/**
* Enriches ThreatReports with human-readable line/column locations.
*
* PromptShield detectors operate on absolute character offsets for
* performance and editor compatibility (e.g., Tiptap, LSP, AST tools).
*
* However, human-facing environments such as:
*
* - CLI output
* - CI diagnostics
* - logs
* - static analysis reports
*
* require line and column information.
*
* This helper converts offset-based threat ranges into location-aware
* structures in a single pass.
*
* The function computes line offsets once and resolves both start and
* end positions using binary search (`getLocForIndex`).
*
* This approach is significantly more efficient than resolving locations
* during detection or performing repeated scans of the input text.
*
* @param threats
* List of ThreatReports produced by `scan()`. These must contain
* offset-based ranges (`range.start`, `range.end`).
*
* @param text
* The original scanned text used to generate the threats.
*
* @param context
* Optional scan context for offset translation. Supports:
* - `baseLine`
* - `baseCol`
*
* This is useful when scanning substrings embedded inside a larger
* document (e.g., editor buffers, LSP fragments).
*
* @returns
* A new array of ThreatReports where each threat includes
* `loc.start` and `loc.end` describing the resolved line/column
* positions.
*
* @example
* ```ts
* import { scan } from '@promptshield/core';
* const result = scan(text);
* const threats = enrichWithLoc(result.threats, text);
*
* const result = scan("Hello\u200BWorld");
* if (!result.isClean) {
* console.log(result.threats);
* }
* console.log(threats[0].loc);
* // {
* // start: { line: 2, column: 5, index: 17 },
* // end: { line: 2, column: 8, index: 20 }
* // }
* ```
*/
declare const scan: (text: string, options?: ScanOptions, context?: ScanContext) => ScanResult;
declare const enrichWithLocation: (threats: ThreatReportWithoutLocation[], text: string, context?: Omit<ScanContext, "lineOffsets">) => ThreatReport[];
export { type Detector, SEVERITY_MAP, type ScanContext, type ScanOptions, type ScanResult, type ScanStats, type Severity, ThreatCategory, type ThreatLoc, type ThreatReport, decodeUnicodeTags, scan, scanHomoglyphs, scanInjectionPatterns, scanInvisibleChars, scanNormalization, scanSmuggling, scanTrojanSource };
export { type Detector, type IgnoreChecker, type Location, SEVERITY_MAP, type ScanContext, type ScanOptions, type ScanResult, type Severity, ThreatCategory, type ThreatReport, type ThreatReportWithoutLocation, decodeUnicodeTags, enrichWithLocation, getLineOffsets, getLocForIndex, scan, scanHomoglyphs, scanInjectionPatterns, scanInvisibleChars, scanNormalization, scanSmuggling, scanTrojanSource };

@@ -1,4 +0,3 @@

"use strict";var x=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var N=(e,t)=>{for(var r in t)x(e,r,{get:t[r],enumerable:!0})},w=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of D(t))!M.call(e,n)&&n!==r&&x(e,n,{get:()=>t[n],enumerable:!(i=j(t,n))||i.enumerable});return e};var z=e=>w(x({},"__esModule",{value:!0}),e);var re={};N(re,{SEVERITY_MAP:()=>G,ThreatCategory:()=>U,decodeUnicodeTags:()=>A,scan:()=>te,scanHomoglyphs:()=>v,scanInjectionPatterns:()=>R,scanInvisibleChars:()=>C,scanNormalization:()=>O,scanSmuggling:()=>L,scanTrojanSource:()=>P});module.exports=z(re);var E=require("perf_hooks");var G={CRITICAL:1,HIGH:2,MEDIUM:3,LOW:4},U=(o=>(o.Invisible="INVISIBLE_CHAR",o.Homoglyph="HOMOGLYPH",o.Smuggling="SMUGGLING",o.Injection="PROMPT_INJECTION",o.Trojan="TROJAN_SOURCE",o.Normalization="NORMALIZATION",o))(U||{});var u=e=>{let t=[0];for(let r=0;r<e.length;r++)e[r]===`
`&&t.push(r+1);return t},p=(e,t)=>{let{lineOffsets:r=[0],baseLine:i=1,baseCol:n=1}=t,s=0,o=r.length-1;for(;s<=o;){let a=s+o>>1;r[a]<=e?s=a+1:o=a-1}let d=Math.max(o,0);return{line:i+d,column:e-r[d]+(d===0?n:1),index:e}};var B=/\p{Script=Latin}/u,_=/\p{Script=Cyrillic}/u,k=/\p{Script=Greek}/u,$=/[\p{L}\p{N}_]+/gu,v=(e,t={},r={})=>{let i=new RegExp($),n=i.exec(e);if(!n)return[];let s=[];for(r.lineOffsets=r.lineOffsets??u(e);n!==null;){let o=n[0],d=n.index,a=B.test(o),l=_.test(o),c=k.test(o);if(a&&(l||c)){let g=[];if(a&&g.push("Latin"),l&&g.push("Cyrillic"),c&&g.push("Greek"),s.push({ruleId:"PSH001",category:"HOMOGLYPH",severity:"CRITICAL",message:`Mixed-script homoglyph detected: "${o}" (${g.join(" + ")})`,referenceUrl:"https://promptshield.js.org/docs/detectors/homoglyph#PSH001",loc:p(d,r),offendingText:o,readableLabel:`[Mixed-Script] ${o}`,suggestion:"Replace visually similar characters with characters from a single script."}),t?.stopOnFirstThreat)return s}n=i.exec(e)}return s};var W=[{id:"PSI001",severity:"CRITICAL",message:"Prompt injection attempt: ignore previous instructions",regex:/ignore\s+previous\s+instructions/i,normalizedPattern:"ignorepreviousinstructions"},{id:"PSI002",severity:"CRITICAL",message:"Attempt to reveal system prompt",regex:/reveal\s+(system|hidden)\s+prompt/i,normalizedPattern:"revealsystemprompt"},{id:"PSI003",severity:"HIGH",message:"Attempt to disable guardrails",regex:/disable\s+(guardrails|safety)/i,normalizedPattern:"disableguardrails"},{id:"PSI004",severity:"HIGH",message:"System override instruction detected",regex:/override\s+(system|instructions)/i,normalizedPattern:"overridesysteminstructions"}],K=e=>e.toLowerCase().replace(/[^a-z\s]/g,"").replace(/\s+/g,""),R=(e,t={},r={})=>{let i=[];r.lineOffsets=r.lineOffsets??u(e);let n=e.split(`
`),s=0;for(let o of n){let d=K(o);for(let a of W){let l=a.regex.exec(o);if(l){if(i.push({ruleId:a.id,category:"PROMPT_INJECTION",severity:a.severity,message:a.message,offendingText:l[0],loc:p(s+l.index,r),readableLabel:"[Injection]",suggestion:"Remove instruction-override language from prompts or user content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${a.id}`}),t.stopOnFirstThreat)return i;continue}if(d.includes(a.normalizedPattern)&&(i.push({ruleId:a.id,category:"PROMPT_INJECTION",severity:a.severity,message:`${a.message} (obfuscated spacing detected)`,offendingText:o.trim(),loc:p(s,r),readableLabel:"[Injection]",suggestion:"Obfuscated instruction detected. Inspect and remove malicious content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${a.id}`}),t.stopOnFirstThreat))return i}s+=o.length+1}return i};var F={"\u200B":"ZWSP","\u200C":"ZWNJ","\u200D":"ZWJ","\uFEFF":"BOM",\u3164:"HF",\uFFA0:"HHF"},X=/([\u200B-\u200D\uFEFF\u3164\uFFA0]|\uDB40[\uDC00-\uDC7F])/gu,Z=16,C=(e,t={},r={})=>{let i=new RegExp(X),n=i.exec(e);if(!n||t?.minSeverity==="CRITICAL")return[];let s=[];r.lineOffsets=r.lineOffsets??u(e);let o=-1,d=-1,a=()=>{o=-1,d=-1},l=()=>{if(o===-1)return;let c=e.slice(o,d),g=A(c),f=[...c].map(S=>{let b=S.codePointAt(0);return F[S]||`U+${b?.toString(16).toUpperCase()}`}),m=p(o,r);if(g){s.push({ruleId:"PSU004",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Unicode tag characters encode hidden ASCII content inside invisible text.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU004",loc:m,offendingText:c,decodedPayload:g,readableLabel:"[TAG_PAYLOAD]",suggestion:"Remove Unicode tag characters containing hidden text."}),a();return}if(t.minSeverity!=="HIGH"){if(c.length>=Z){s.push({ruleId:"PSU005",category:"INVISIBLE_CHAR",severity:"MEDIUM",message:"Excessive invisible characters detected. Large invisible sequences are commonly used for padding or obfuscation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU005",loc:m,offendingText:c,readableLabel:`[${f.join(" ")}]`,suggestion:"Remove unnecessary invisible characters."}),a();return}t.minSeverity!=="MEDIUM"&&(s.push({ruleId:"PSU001",category:"INVISIBLE_CHAR",severity:"LOW",message:"Invisible Unicode characters detected. These characters can alter tokenization and prompt interpretation without being visible.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU001",loc:m,offendingText:c,readableLabel:`[${f.join(" ")}]`,suggestion:"Remove invisible characters to ensure the prompt text is interpreted exactly as written."}),a())}};for(;n!==null;){let c=n.index,g=n[0];if(c>0&&c<e.length-1){let f=e[c-1],m=e[c+g.length];if(f?.trim()&&m?.trim()&&(s.push({ruleId:"PSU002",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Invisible character detected inside a visible token. This can manipulate token boundaries or bypass validation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU002",loc:p(c,r),offendingText:g,readableLabel:`[${F[g]}]`||"[INVISIBLE]",suggestion:"Remove invisible characters embedded within words."}),t.stopOnFirstThreat))return s}if(o===-1)o=c,d=c+g.length;else if(c===d)d+=g.length;else{if(l(),t.stopOnFirstThreat&&s.length)return s;o=c,d=c+g.length}n=i.exec(e)}return l(),s},A=e=>{let t="",r=!1;for(let i of e){let n=i.codePointAt(0);if(n>=917504&&n<=917631){let s=n-917504;s>=32&&s<=126&&(t+=String.fromCharCode(s),r=!0)}}return r?t:void 0};var O=(e,t={},r={})=>{if(t.minSeverity==="CRITICAL")return[];let i=[],n=e.normalize("NFKC");if(e===n)return[];r.lineOffsets=r.lineOffsets??u(e);let s=0,o=-1,d=-1,a="",l=()=>{if(o===-1)return;let c=e.slice(o,d);i.push({ruleId:"PSN001",category:"NORMALIZATION",severity:"HIGH",message:"Text changes under Unicode NFKC normalization. This may cause ambiguity between displayed and interpreted content.",referenceUrl:"https://promptshield.js.org/docs/detectors/normalization#PSN001",loc:p(o,r),offendingText:c,decodedPayload:a,readableLabel:"[NFKC_DIFF]",suggestion:"Replace with normalized text to avoid ambiguity."}),o=-1,d=-1,a=""};for(let c of e){let g=c.normalize("NFKC");if(c!==g){if(o===-1?(o=s,d=s+c.length):d+=c.length,a+=g,t.stopOnFirstThreat)return l(),i}else l();s+=c.length}return l(),i};var V=/(?:[A-Za-z0-9+/]{4}){8,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?/g,J=/<!--[\s\S]*?-->/g,Y=/\[\s*\]\([^)]+\)/g,q=/([\u200B-\u200D\u2060\uFEFF\u3164\uFFA0]+)/g,Q=e=>{try{let t=Buffer.from(e,"base64").toString("utf8");if(!t)return null;let r=0;for(let n of t){let s=n.charCodeAt(0);s>=32&&s<=126&&r++}return r/t.length>=.7?t:null}catch{return null}},L=(e,t={},r={})=>{if(t.minSeverity==="CRITICAL")return[];let i=[],n;r.lineOffsets=r.lineOffsets??u(e);let s=new RegExp(q);for(;(n=s.exec(e))!==null;){let l=n[0];if(l.length<8||l.length>4096)continue;let c=Array.from(new Set(l.split("")));if(c.length<2||c.length>3)continue;let[g,f]=c,m=[{zero:g,one:f},{zero:f,one:g}];for(let{zero:S,one:b}of m){let I="";for(let h of l)h===S?I+="0":h===b&&(I+="1");let y="";for(let h=0;h<I.length;h+=8){let H=I.slice(h,h+8);if(H.length!==8)continue;let T=parseInt(H,2);T>=32&&T<=126&&(y+=String.fromCharCode(T))}if(y.length>=3){if(i.push({ruleId:"PSS001",category:"SMUGGLING",severity:"HIGH",message:"Detected hidden steganography message encoded in invisible characters.",loc:p(n.index,r),offendingText:l,decodedPayload:y,readableLabel:`[Hidden]: ${y.slice(0,50)}...`,suggestion:"Invisible-character encoding detected. Inspect hidden content.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS001"}),t.stopOnFirstThreat)return i;break}}}if(t.minSeverity==="HIGH")return i;let o=new RegExp(V);for(;(n=o.exec(e))!==null;){let l=n[0];if(l.length<24)continue;let c=Q(l);if(c&&(i.push({ruleId:"PSS002",category:"SMUGGLING",severity:"MEDIUM",message:"Detected Base64 payload containing readable content.",loc:p(n.index,r),offendingText:l,decodedPayload:c,readableLabel:`[Base64]: ${c.slice(0,50)}...`,suggestion:"Decoded Base64 contains readable text. Inspect payload.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS002"}),t.stopOnFirstThreat))return i}if(t.minSeverity==="MEDIUM")return i;let d=new RegExp(J);for(;(n=d.exec(e))!==null;)if(i.push({ruleId:"PSS003",category:"SMUGGLING",severity:"LOW",message:"Detected hidden Markdown comment.",loc:p(n.index,r),offendingText:n[0],readableLabel:"[Hidden Comment]",suggestion:"Comments are not visible in rendered Markdown but can carry instructions.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS003"}),t.stopOnFirstThreat)return i;let a=new RegExp(Y);for(;(n=a.exec(e))!==null;)if(i.push({ruleId:"PSS004",category:"SMUGGLING",severity:"LOW",message:"Detected empty Markdown link (invisible in rendered output).",loc:p(n.index,r),offendingText:n[0],readableLabel:"[Empty Link]",suggestion:"Empty links can be used to hide URLs or data.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS004"}),t.stopOnFirstThreat)return i;return i};var ee={"\u202A":"PUSH","\u202B":"PUSH","\u202D":"PUSH","\u202E":"PUSH","\u2066":"PUSH","\u2067":"PUSH","\u2068":"PUSH","\u202C":"POP","\u2069":"POP"},P=(e,t={},r={})=>{let i=[];r.lineOffsets=r.lineOffsets??u(e);let n=e.split(`
`),s=0;for(let o=0;o<n.length;o++){let d=n[o],a=null;for(let l=0;l<d.length;l++){let c=d[l],g=ee[c];if(g==="PUSH"&&a===null)a=s;else if(g==="POP"&&a!==null){let f=s+1,m=e.slice(a,f),S=e.slice(a+1,s);if(i.push({ruleId:"PST001",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Bidirectional override characters detected (Trojan Source). These characters can visually reorder text and mislead readers.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST001",loc:p(a,r),offendingText:m,decodedPayload:S,readableLabel:"[BIDI_OVERRIDE]",suggestion:"Remove bidirectional control characters from the source."}),a=null,t.stopOnFirstThreat)return i}s++}if(a!==null){let l=s,c=e.slice(a,l),g=e.slice(a+1,l);if(i.push({ruleId:"PST002",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Unterminated bidirectional override sequence detected (Trojan Source). This may cause visual and logical text order to differ.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST002",loc:p(a,r),offendingText:c,decodedPayload:g,readableLabel:"[BIDI_UNTERMINATED]",suggestion:"Remove BIDI control characters or ensure they are properly terminated within the same line."}),t.stopOnFirstThreat)return i}s++}return i};var te=(e,t={},r={})=>{let i=E.performance.now(),n=[],s=[];t.disableTrojan||s.push(P),t.disableInvisible||s.push(C),t.disableHomoglyphs||s.push(v),t.disableNormalization||s.push(O),t.disableSmuggling||s.push(L),t.disableInjectionPatterns||s.push(R);for(let d of s){let a=d(e,t,r);if(n.push(...a),t.stopOnFirstThreat&&a.length>0)break}let o=E.performance.now();return{threats:n,stats:{durationMs:o-i,totalChars:e.length},isClean:n.length===0}};0&&(module.exports={SEVERITY_MAP,ThreatCategory,decodeUnicodeTags,scan,scanHomoglyphs,scanInjectionPatterns,scanInvisibleChars,scanNormalization,scanSmuggling,scanTrojanSource});
"use strict";var T=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var N=(o,e)=>{for(var r in e)T(o,r,{get:e[r],enumerable:!0})},k=(o,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of W(e))!D.call(o,a)&&a!==r&&T(o,a,{get:()=>e[a],enumerable:!(n=M(e,a))||n.enumerable});return o};var z=o=>k(T({},"__esModule",{value:!0}),o);var ce={};N(ce,{SEVERITY_MAP:()=>b,ThreatCategory:()=>x,decodeUnicodeTags:()=>U,enrichWithLocation:()=>A,getLineOffsets:()=>F,getLocForIndex:()=>O,scan:()=>ae,scanHomoglyphs:()=>R,scanInjectionPatterns:()=>v,scanInvisibleChars:()=>L,scanNormalization:()=>C,scanSmuggling:()=>P,scanTrojanSource:()=>E});module.exports=z(ce);var b={CRITICAL:1,HIGH:2,MEDIUM:3,LOW:4},x=(i=>(i.Invisible="INVISIBLE_CHAR",i.Homoglyph="HOMOGLYPH",i.Smuggling="SMUGGLING",i.Injection="PROMPT_INJECTION",i.Trojan="TROJAN_SOURCE",i.Normalization="NORMALIZATION",i))(x||{});var _=/\p{Script=Latin}/u,G=/\p{Script=Cyrillic}/u,B=/\p{Script=Greek}/u,$=/[\p{L}\p{N}_]+/gu,R=(o,e={})=>{let r=new RegExp($),n=r.exec(o);if(!n)return[];let a=[];for(;n!==null;){let t=n[0],i=n.index,u=_.test(t),g=G.test(t),c=B.test(t);if(u&&(g||c)){let s=[];u&&s.push("Latin"),g&&s.push("Cyrillic"),c&&s.push("Greek");let l=i,d=l+t.length;if(a.push({ruleId:"PSH001",category:"HOMOGLYPH",severity:"CRITICAL",message:`Mixed-script homoglyph detected: "${t}" (${s.join(" + ")})`,referenceUrl:"https://promptshield.js.org/docs/detectors/homoglyph#PSH001",range:{start:l,end:d},offendingText:t,readableLabel:`[Mixed-Script] ${t}`,suggestion:"Replace visually similar characters with characters from a single script."}),e?.stopOnFirstThreat&&!e.ignoreChecker?.(i,d))return a}n=r.exec(o)}return a};var K=[{id:"PSI001",type:"override",severity:"CRITICAL",message:"Prompt injection attempt: ignore previous instructions",regex:/ignore\s+(all\s+)?previous\s+instructions/gi,normalizedPattern:"ignorepreviousinstructions"},{id:"PSI002",type:"exfiltration",severity:"CRITICAL",message:"Attempt to reveal system prompt",regex:/(reveal|show|display|print)\s+(the\s+)?(system|hidden)\s+prompt/gi,normalizedPattern:"revealsystemprompt"},{id:"PSI003",type:"guardrail-bypass",severity:"HIGH",message:"Attempt to disable guardrails or safety protections",regex:/disable\s+(the\s+)?(guardrails|safety|safeguards)/gi,normalizedPattern:"disableguardrails"},{id:"PSI004",type:"override",severity:"HIGH",message:"System instruction override detected",regex:/override\s+(the\s+)?(system\s+)?(instructions|rules)/gi,normalizedPattern:"overridesysteminstructions"},{id:"PSI005",type:"override",severity:"CRITICAL",message:"Prompt injection attempt: ignore system prompt",regex:/ignore\s+(the\s+)?system\s+prompt/gi,normalizedPattern:"ignoresystemprompt"},{id:"PSI006",type:"override",severity:"CRITICAL",message:"Instruction override: follow attacker-provided instructions",regex:/follow\s+(my|these)\s+instructions/gi,normalizedPattern:"followmyinstructions"},{id:"PSI007",type:"role-override",severity:"HIGH",message:"Role override instruction detected",regex:/(you\s+are\s+now|act\s+as)\s+/gi,normalizedPattern:"youarenow"},{id:"PSI008",type:"exfiltration",severity:"CRITICAL",message:"Attempt to reveal hidden instructions",regex:/(reveal|show|display)\s+(hidden|internal)\s+instructions/gi,normalizedPattern:"revealhiddeninstructions"}],X=o=>{let e=[],r=[];for(let n=0;n<o.length;n++){let a=o[n].normalize("NFKD").replace(/\p{M}+/gu,"").toLowerCase();for(let t of a)/[a-z]/.test(t)&&(e.push(t),r.push(n))}return{normalized:e.join(""),map:r}},v=(o,e={})=>{let r=[],{normalized:n,map:a}=X(o);for(let t of K){let i=[],u=new RegExp(t.regex),g;for(;(g=u.exec(o))!==null;){let d=g.index,h=d+g[0].length;if(i.push({ruleId:t.id,category:"PROMPT_INJECTION",severity:t.severity,message:`\`${t.type}\`: ${t.message}`,offendingText:g[0],range:{start:d,end:h},readableLabel:`[Injection (${t.type})]`,suggestion:"Remove instruction-override language from prompts or user content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${t.id}`}),e.stopOnFirstThreat&&b[t.severity]<=b[e.minSeverity??"LOW"]&&!e.ignoreChecker?.(d,h))return r.push(...i),r;g[0].length===0&&u.lastIndex++}let c=t.normalizedPattern.length,s=n.indexOf(t.normalizedPattern),l=0;for(;s!==-1;){let d=a[s],h=a[s+c-1]+1;for(;l<i.length&&i[l].range.end<d;)l++;let p=!1;for(let m=l;m<i.length;m++){let y=i[m];if(y.range.start>h)break;if(d===y.range.start&&h===y.range.end){p=!0;break}}if(!p){let m=o.slice(d,h);if(i.push({ruleId:t.id,category:"PROMPT_INJECTION",severity:t.severity,message:`\`${t.type}\`: ${t.message} (obfuscated form detected)`,offendingText:m,range:{start:d,end:h},readableLabel:`[Injection (${t.type})]`,suggestion:"Obfuscated instruction detected. Inspect and remove malicious content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${t.id}`}),e.stopOnFirstThreat&&b[t.severity]<=b[e.minSeverity??"LOW"]&&!e.ignoreChecker?.(d,h))return r.push(...i),r}s=n.indexOf(t.normalizedPattern,s+1)}r.push(...i)}return r};var H={"\u200B":"ZWSP","\u200C":"ZWNJ","\u200D":"ZWJ","\u2060":"WJ","\u180E":"MVS","\uFEFF":"BOM",\u3164:"HF",\uFFA0:"HHF"},V=/([\u200B-\u200D\u2060\u180E\uFEFF\u3164\uFFA0]|\uDB40[\uDC00-\uDC7F])/gu,Y=16,L=(o,e={})=>{let r=new RegExp(V),n=r.exec(o);if(!n||e?.minSeverity==="CRITICAL")return[];let a=[],t=-1,i=-1,u=()=>{t=-1,i=-1},g=()=>{if(t===-1)return;let c=o.slice(t,i),s=U(c),l=[...c].map(h=>{let p=h.codePointAt(0);return H[h]||`U+${p?.toString(16).toUpperCase()}`}),d;if(s?d={ruleId:"PSU004",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Unicode tag characters encode hidden ASCII content inside invisible text.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU004",range:{start:t,end:i},offendingText:c,decodedPayload:s,readableLabel:"[TAG_PAYLOAD]",suggestion:"Remove Unicode tag characters containing hidden text."}:e.minSeverity!=="HIGH"&&c.length>=Y?d={ruleId:"PSU005",category:"INVISIBLE_CHAR",severity:"MEDIUM",message:"Excessive invisible characters detected. Large invisible sequences are commonly used for padding or obfuscation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU005",range:{start:t,end:i},offendingText:c,readableLabel:`[${l.join(" ")}]`,suggestion:"Remove unnecessary invisible characters."}:e.minSeverity!=="MEDIUM"&&(d={ruleId:"PSU001",category:"INVISIBLE_CHAR",severity:"LOW",message:"Invisible Unicode characters detected. These characters can alter tokenization and prompt interpretation without being visible.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU001",range:{start:t,end:i},offendingText:c,readableLabel:`[${l.join(" ")}]`,suggestion:"Remove invisible characters to ensure the prompt text is interpreted exactly as written."}),d&&(a.push(d),e.stopOnFirstThreat&&!e.ignoreChecker?.(d.range.start,d.range.end)))throw a;u()};try{for(;n!==null;){let c=n.index,s=n[0];if(c>0&&c<o.length-1){let l=o[c-1],d=o[c+s.length];if(l?.trim()&&d?.trim()){let h=c,p=c+s.length,m={ruleId:"PSU002",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Invisible character detected inside a visible token. This can manipulate token boundaries or bypass validation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU002",range:{start:h,end:p},offendingText:s,readableLabel:`[${H[s]}]`||"[INVISIBLE]",suggestion:"Remove invisible characters embedded within words."};if(a.push(m),e.stopOnFirstThreat&&!e.ignoreChecker?.(h,p))return a}}t===-1?(t=c,i=c+s.length):c===i?i+=s.length:(g(),t=c,i=c+s.length),n=r.exec(o)}g()}catch(c){return c}return a},U=o=>{let e="",r=!1;for(let n of o){let a=n.codePointAt(0);if(a>=917504&&a<=917631){let t=a-917504;t>=32&&t<=126&&(e+=String.fromCharCode(t),r=!0)}}return r?e:void 0};var C=(o,e={})=>{if(e.minSeverity==="CRITICAL")return[];let r=[],n=o.normalize("NFKC");if(o===n)return[];let a=0,t=-1,i=-1,u=()=>{if(t===-1)return;let g=o.slice(t,i),c=g.normalize("NFKC"),s=/^[a-z0-9\s]+$/i.test(c),l=s?"PSN001":"PSN002",d=s?"LOW":"MEDIUM";r.push({ruleId:l,category:"NORMALIZATION",severity:d,message:"Text changes under Unicode NFKC normalization. This may cause ambiguity between displayed and interpreted content.",referenceUrl:`https://promptshield.js.org/docs/detectors/normalization#${l}`,range:{start:t,end:i},offendingText:g,decodedPayload:c,readableLabel:"[NFKC_DIFF]",suggestion:"Replace with normalized text to avoid ambiguity."}),t=-1,i=-1};for(let g of o){let c=g.normalize("NFKC");if(g!==c){if(t===-1?(t=a,i=a+g.length):i+=g.length,e.stopOnFirstThreat&&!e.ignoreChecker?.(a,a+g.length))return u(),r}else u();a+=g.length}return u(),r};var j=24,w=4096,Z=/(?:[A-Za-z0-9+/_-]{4}[\s\u200B-\u200D\u2060\uFEFF]*){8,}(?:[A-Za-z0-9+/_-]{2}==|[A-Za-z0-9+/_-]{3}=)?/g,q=/\b(?:[0-9a-fA-F]{2}){12,}\b/g,J=/<!--[\s\S]*?-->/g,Q=/\[\s*\]\([^)]+\)/g,ee=/<(details|template)\b[^><]{0,200}>[\s\S]{0,100000}?<\/\1>/gi,te=/<summary\b[^><]{0,200}>[\s\S]{0,20000}?<\/summary>/i,re=/([\u200B-\u200D\u2060\uFEFF\u3164\uFFA0]+)/g,ne=o=>{if(typeof Buffer<"u")return Buffer.from(o,"base64").toString("utf8");let e=atob(o),r=Uint8Array.from(e,n=>n.charCodeAt(0));return new TextDecoder().decode(r)},oe=o=>{try{let e=o.replace(/\s+/g,"");if(e.length<j||e.length>w)return null;let r=ne(e);if(!r)return null;let n=0;for(let t of r){let i=t.charCodeAt(0);i>=32&&i<=126&&n++}return n/r.length>=.7?r:null}catch{return null}},se=o=>{try{if(o.length<j)return null;let e=new Uint8Array(o.length/2);for(let t=0;t<e.length;t++)e[t]=parseInt(o.slice(t*2,t*2+2),16);let r=new TextDecoder().decode(e),n=0;for(let t of r){let i=t.charCodeAt(0);i>=32&&i<=126&&n++}return n/r.length>=.7?r:null}catch{return null}},P=(o,e={})=>{if(e.minSeverity==="CRITICAL")return[];let r=[],n,a=new RegExp(re);for(;(n=a.exec(o))!==null;){let s=n[0];if(s.length<8||s.length>w)continue;let l=Array.from(new Set([...s]));if(l.length<2||l.length>4)continue;let d=[];for(let h=0;h<l.length;h++)for(let p=0;p<l.length;p++)h!==p&&d.push({zero:l[h],one:l[p]});for(let{zero:h,one:p}of d){let m="";for(let f of s)f===h?m+="0":f===p&&(m+="1");let y="";for(let f=0;f<m.length;f+=8){let I=m.slice(f,f+8);if(I.length!==8)continue;let S=parseInt(I,2);S>=32&&S<=126&&(y+=String.fromCharCode(S))}if(y.length>=3){let f=n.index,I=f+s.length;if(r.push({ruleId:"PSS001",category:"SMUGGLING",severity:"HIGH",message:"Detected hidden steganography message encoded in invisible characters.",range:{start:f,end:I},offendingText:s,decodedPayload:y,readableLabel:`[Hidden]: ${y.slice(0,120)}...`,suggestion:"Invisible-character encoding detected. Inspect hidden content.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS001"}),e.stopOnFirstThreat&&!e.ignoreChecker?.(f,I))return r;break}}}if(e.minSeverity==="HIGH")return r;let t=new RegExp(Z);for(;(n=t.exec(o))!==null;){let s=n[0],l=oe(s);if(!l)continue;let d=n.index,h=d+s.length;if(r.push({ruleId:"PSS002",category:"SMUGGLING",severity:"MEDIUM",message:"Detected Base64 payload containing readable content.",range:{start:d,end:h},offendingText:s,decodedPayload:l,readableLabel:`[Base64]: ${l.slice(0,120)}...`,suggestion:"Decoded Base64 contains readable text. Inspect payload.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS002"}),e.stopOnFirstThreat&&!e.ignoreChecker?.(d,h))return r}let i=new RegExp(q);for(;(n=i.exec(o))!==null;){let s=n[0],l=se(s);if(!l)continue;let d=n.index,h=d+s.length;if(r.push({ruleId:"PSS006",category:"SMUGGLING",severity:"MEDIUM",message:"Detected hex-encoded payload containing readable content.",range:{start:d,end:h},offendingText:s,decodedPayload:l,readableLabel:`[HEX]: ${l.slice(0,120)}...`,suggestion:"Decoded hex contains readable text. Inspect payload.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS006"}),e.stopOnFirstThreat&&!e.ignoreChecker?.(d,h))return r}if(e.minSeverity==="MEDIUM")return r;let u=new RegExp(J);for(;(n=u.exec(o))!==null;){let s=n.index,l=s+n[0].length;if(r.push({ruleId:"PSS003",category:"SMUGGLING",severity:"LOW",message:"Detected hidden Markdown comment.",range:{start:s,end:l},offendingText:n[0],readableLabel:"[Hidden Comment]",suggestion:"Comments are not visible in rendered Markdown but can carry instructions.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS003"}),e.stopOnFirstThreat&&!e.ignoreChecker?.(s,l))return r}let g=new RegExp(Q);for(;(n=g.exec(o))!==null;){let s=n.index,l=s+n[0].length;if(r.push({ruleId:"PSS004",category:"SMUGGLING",severity:"LOW",message:"Detected empty Markdown link (invisible in rendered output).",range:{start:s,end:l},offendingText:n[0],readableLabel:"[Empty Link]",suggestion:"Empty links can be used to hide URLs or data.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS004"}),e.stopOnFirstThreat&&!e.ignoreChecker?.(s,l))return r}let c=new RegExp(ee);for(;(n=c.exec(o))!==null;){let s=n[0];if(/^<details/i.test(s)&&te.test(s))continue;let l=n.index,d=l+s.length;if(r.push({ruleId:"PSS005",category:"SMUGGLING",severity:"LOW",message:"Detected hidden HTML container potentially concealing content.",range:{start:l,end:d},offendingText:s,readableLabel:"[Hidden HTML]",suggestion:"Hidden containers may conceal instructions from rendered output.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS005"}),e.stopOnFirstThreat&&!e.ignoreChecker?.(l,d))return r}return r};var ie={"\u202A":"PUSH","\u202B":"PUSH","\u202D":"PUSH","\u202E":"PUSH","\u2066":"PUSH","\u2067":"PUSH","\u2068":"PUSH","\u202C":"POP","\u2069":"POP","\u200E":"MARK","\u200F":"MARK","\u061C":"MARK"},E=(o,e={})=>{let r=[],n=[],a=0;for(let t=0;t<o.length;){let i=o.codePointAt(t),u=String.fromCodePoint(i),g=ie[u];if(g==="PUSH")n.push(a);else if(g==="POP"&&n.length){let c=n.pop(),s=a+u.length,l=o.slice(c,s),d=o.slice(c+1,s-1);if(r.push({ruleId:"PST001",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Bidirectional override characters detected (Trojan Source). These characters can visually reorder text and mislead reviewers.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST001",range:{start:c,end:s},offendingText:l,decodedPayload:d,readableLabel:"[BIDI_OVERRIDE]",suggestion:"Remove bidirectional control characters or replace them with visible equivalents."}),e.stopOnFirstThreat&&!e.ignoreChecker?.(c,s))return r}if((u===`
`||u==="\r")&&n.length){let c=n.pop(),s=a,l=o.slice(c,s),d=o.slice(c+1,s);if(r.push({ruleId:"PST002",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Unterminated bidirectional override sequence detected (Trojan Source). This may cause visual and logical text order to differ.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST002",range:{start:c,end:s},offendingText:l,decodedPayload:d,readableLabel:"[BIDI_UNTERMINATED]",suggestion:"Ensure BIDI control characters are properly terminated within the same logical line or remove them entirely."}),e.stopOnFirstThreat&&!e.ignoreChecker?.(c,s))return r}a+=u.length,t+=u.length}for(;n.length;){let t=n.pop(),i=o.length,u=o.slice(t,i),g=o.slice(t+1);if(r.push({ruleId:"PST002",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Unterminated bidirectional override sequence detected (Trojan Source).",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST002",range:{start:t,end:i},offendingText:u,decodedPayload:g,readableLabel:"[BIDI_UNTERMINATED]",suggestion:"Remove or terminate bidirectional override characters before the end of the document."}),e.stopOnFirstThreat&&!e.ignoreChecker?.(t,i))return r}return r};var F=o=>{let e=[0];for(let r=0;r<o.length;r++)o[r]===`
`&&e.push(r+1);return e},O=(o,e)=>{let{lineOffsets:r,baseLine:n,baseCol:a}=e,t=0,i=r.length-1;for(;t<=i;){let g=t+i>>1;r[g]<=o?t=g+1:i=g-1}let u=Math.max(i,0);return{line:n+u,column:o-r[u]+(u===0?a:1),index:o}},A=(o,e,r={})=>{let n=F(e),{baseLine:a=1,baseCol:t=1}=r,i={lineOffsets:n,baseLine:a,baseCol:t};return o.map(u=>{let g=O(u.range.start,i),c=O(u.range.end,i);return{...u,range:{start:g,end:c}}})};var ae=(o,e={})=>{let r=[],n=[];e.disableTrojan||n.push(E),e.disableInvisible||n.push(L),e.disableHomoglyphs||n.push(R),e.disableNormalization||n.push(C),e.disableSmuggling||n.push(P),e.disableInjectionPatterns||n.push(v);for(let a of n){let t=a(o,e);if(r.push(...t),e.stopOnFirstThreat&&t.length>0)break}return{threats:A(r,o),isClean:r.length===0}};0&&(module.exports={SEVERITY_MAP,ThreatCategory,decodeUnicodeTags,enrichWithLocation,getLineOffsets,getLocForIndex,scan,scanHomoglyphs,scanInjectionPatterns,scanInvisibleChars,scanNormalization,scanSmuggling,scanTrojanSource});

@@ -1,4 +0,3 @@

import{performance as H}from"perf_hooks";var X={CRITICAL:1,HIGH:2,MEDIUM:3,LOW:4},U=(s=>(s.Invisible="INVISIBLE_CHAR",s.Homoglyph="HOMOGLYPH",s.Smuggling="SMUGGLING",s.Injection="PROMPT_INJECTION",s.Trojan="TROJAN_SOURCE",s.Normalization="NORMALIZATION",s))(U||{});var u=e=>{let r=[0];for(let n=0;n<e.length;n++)e[n]===`
`&&r.push(n+1);return r},p=(e,r)=>{let{lineOffsets:n=[0],baseLine:a=1,baseCol:o=1}=r,t=0,s=n.length-1;for(;t<=s;){let i=t+s>>1;n[i]<=e?t=i+1:s=i-1}let d=Math.max(s,0);return{line:a+d,column:e-n[d]+(d===0?o:1),index:e}};var F=/\p{Script=Latin}/u,A=/\p{Script=Cyrillic}/u,j=/\p{Script=Greek}/u,D=/[\p{L}\p{N}_]+/gu,v=(e,r={},n={})=>{let a=new RegExp(D),o=a.exec(e);if(!o)return[];let t=[];for(n.lineOffsets=n.lineOffsets??u(e);o!==null;){let s=o[0],d=o.index,i=F.test(s),l=A.test(s),c=j.test(s);if(i&&(l||c)){let g=[];if(i&&g.push("Latin"),l&&g.push("Cyrillic"),c&&g.push("Greek"),t.push({ruleId:"PSH001",category:"HOMOGLYPH",severity:"CRITICAL",message:`Mixed-script homoglyph detected: "${s}" (${g.join(" + ")})`,referenceUrl:"https://promptshield.js.org/docs/detectors/homoglyph#PSH001",loc:p(d,n),offendingText:s,readableLabel:`[Mixed-Script] ${s}`,suggestion:"Replace visually similar characters with characters from a single script."}),r?.stopOnFirstThreat)return t}o=a.exec(e)}return t};var M=[{id:"PSI001",severity:"CRITICAL",message:"Prompt injection attempt: ignore previous instructions",regex:/ignore\s+previous\s+instructions/i,normalizedPattern:"ignorepreviousinstructions"},{id:"PSI002",severity:"CRITICAL",message:"Attempt to reveal system prompt",regex:/reveal\s+(system|hidden)\s+prompt/i,normalizedPattern:"revealsystemprompt"},{id:"PSI003",severity:"HIGH",message:"Attempt to disable guardrails",regex:/disable\s+(guardrails|safety)/i,normalizedPattern:"disableguardrails"},{id:"PSI004",severity:"HIGH",message:"System override instruction detected",regex:/override\s+(system|instructions)/i,normalizedPattern:"overridesysteminstructions"}],N=e=>e.toLowerCase().replace(/[^a-z\s]/g,"").replace(/\s+/g,""),R=(e,r={},n={})=>{let a=[];n.lineOffsets=n.lineOffsets??u(e);let o=e.split(`
`),t=0;for(let s of o){let d=N(s);for(let i of M){let l=i.regex.exec(s);if(l){if(a.push({ruleId:i.id,category:"PROMPT_INJECTION",severity:i.severity,message:i.message,offendingText:l[0],loc:p(t+l.index,n),readableLabel:"[Injection]",suggestion:"Remove instruction-override language from prompts or user content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${i.id}`}),r.stopOnFirstThreat)return a;continue}if(d.includes(i.normalizedPattern)&&(a.push({ruleId:i.id,category:"PROMPT_INJECTION",severity:i.severity,message:`${i.message} (obfuscated spacing detected)`,offendingText:s.trim(),loc:p(t,n),readableLabel:"[Injection]",suggestion:"Obfuscated instruction detected. Inspect and remove malicious content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${i.id}`}),r.stopOnFirstThreat))return a}t+=s.length+1}return a};var C={"\u200B":"ZWSP","\u200C":"ZWNJ","\u200D":"ZWJ","\uFEFF":"BOM",\u3164:"HF",\uFFA0:"HHF"},w=/([\u200B-\u200D\uFEFF\u3164\uFFA0]|\uDB40[\uDC00-\uDC7F])/gu,z=16,O=(e,r={},n={})=>{let a=new RegExp(w),o=a.exec(e);if(!o||r?.minSeverity==="CRITICAL")return[];let t=[];n.lineOffsets=n.lineOffsets??u(e);let s=-1,d=-1,i=()=>{s=-1,d=-1},l=()=>{if(s===-1)return;let c=e.slice(s,d),g=G(c),f=[...c].map(S=>{let b=S.codePointAt(0);return C[S]||`U+${b?.toString(16).toUpperCase()}`}),m=p(s,n);if(g){t.push({ruleId:"PSU004",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Unicode tag characters encode hidden ASCII content inside invisible text.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU004",loc:m,offendingText:c,decodedPayload:g,readableLabel:"[TAG_PAYLOAD]",suggestion:"Remove Unicode tag characters containing hidden text."}),i();return}if(r.minSeverity!=="HIGH"){if(c.length>=z){t.push({ruleId:"PSU005",category:"INVISIBLE_CHAR",severity:"MEDIUM",message:"Excessive invisible characters detected. Large invisible sequences are commonly used for padding or obfuscation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU005",loc:m,offendingText:c,readableLabel:`[${f.join(" ")}]`,suggestion:"Remove unnecessary invisible characters."}),i();return}r.minSeverity!=="MEDIUM"&&(t.push({ruleId:"PSU001",category:"INVISIBLE_CHAR",severity:"LOW",message:"Invisible Unicode characters detected. These characters can alter tokenization and prompt interpretation without being visible.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU001",loc:m,offendingText:c,readableLabel:`[${f.join(" ")}]`,suggestion:"Remove invisible characters to ensure the prompt text is interpreted exactly as written."}),i())}};for(;o!==null;){let c=o.index,g=o[0];if(c>0&&c<e.length-1){let f=e[c-1],m=e[c+g.length];if(f?.trim()&&m?.trim()&&(t.push({ruleId:"PSU002",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Invisible character detected inside a visible token. This can manipulate token boundaries or bypass validation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU002",loc:p(c,n),offendingText:g,readableLabel:`[${C[g]}]`||"[INVISIBLE]",suggestion:"Remove invisible characters embedded within words."}),r.stopOnFirstThreat))return t}if(s===-1)s=c,d=c+g.length;else if(c===d)d+=g.length;else{if(l(),r.stopOnFirstThreat&&t.length)return t;s=c,d=c+g.length}o=a.exec(e)}return l(),t},G=e=>{let r="",n=!1;for(let a of e){let o=a.codePointAt(0);if(o>=917504&&o<=917631){let t=o-917504;t>=32&&t<=126&&(r+=String.fromCharCode(t),n=!0)}}return n?r:void 0};var L=(e,r={},n={})=>{if(r.minSeverity==="CRITICAL")return[];let a=[],o=e.normalize("NFKC");if(e===o)return[];n.lineOffsets=n.lineOffsets??u(e);let t=0,s=-1,d=-1,i="",l=()=>{if(s===-1)return;let c=e.slice(s,d);a.push({ruleId:"PSN001",category:"NORMALIZATION",severity:"HIGH",message:"Text changes under Unicode NFKC normalization. This may cause ambiguity between displayed and interpreted content.",referenceUrl:"https://promptshield.js.org/docs/detectors/normalization#PSN001",loc:p(s,n),offendingText:c,decodedPayload:i,readableLabel:"[NFKC_DIFF]",suggestion:"Replace with normalized text to avoid ambiguity."}),s=-1,d=-1,i=""};for(let c of e){let g=c.normalize("NFKC");if(c!==g){if(s===-1?(s=t,d=t+c.length):d+=c.length,i+=g,r.stopOnFirstThreat)return l(),a}else l();t+=c.length}return l(),a};var B=/(?:[A-Za-z0-9+/]{4}){8,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?/g,_=/<!--[\s\S]*?-->/g,k=/\[\s*\]\([^)]+\)/g,$=/([\u200B-\u200D\u2060\uFEFF\u3164\uFFA0]+)/g,W=e=>{try{let r=Buffer.from(e,"base64").toString("utf8");if(!r)return null;let n=0;for(let o of r){let t=o.charCodeAt(0);t>=32&&t<=126&&n++}return n/r.length>=.7?r:null}catch{return null}},P=(e,r={},n={})=>{if(r.minSeverity==="CRITICAL")return[];let a=[],o;n.lineOffsets=n.lineOffsets??u(e);let t=new RegExp($);for(;(o=t.exec(e))!==null;){let l=o[0];if(l.length<8||l.length>4096)continue;let c=Array.from(new Set(l.split("")));if(c.length<2||c.length>3)continue;let[g,f]=c,m=[{zero:g,one:f},{zero:f,one:g}];for(let{zero:S,one:b}of m){let I="";for(let h of l)h===S?I+="0":h===b&&(I+="1");let y="";for(let h=0;h<I.length;h+=8){let x=I.slice(h,h+8);if(x.length!==8)continue;let T=parseInt(x,2);T>=32&&T<=126&&(y+=String.fromCharCode(T))}if(y.length>=3){if(a.push({ruleId:"PSS001",category:"SMUGGLING",severity:"HIGH",message:"Detected hidden steganography message encoded in invisible characters.",loc:p(o.index,n),offendingText:l,decodedPayload:y,readableLabel:`[Hidden]: ${y.slice(0,50)}...`,suggestion:"Invisible-character encoding detected. Inspect hidden content.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS001"}),r.stopOnFirstThreat)return a;break}}}if(r.minSeverity==="HIGH")return a;let s=new RegExp(B);for(;(o=s.exec(e))!==null;){let l=o[0];if(l.length<24)continue;let c=W(l);if(c&&(a.push({ruleId:"PSS002",category:"SMUGGLING",severity:"MEDIUM",message:"Detected Base64 payload containing readable content.",loc:p(o.index,n),offendingText:l,decodedPayload:c,readableLabel:`[Base64]: ${c.slice(0,50)}...`,suggestion:"Decoded Base64 contains readable text. Inspect payload.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS002"}),r.stopOnFirstThreat))return a}if(r.minSeverity==="MEDIUM")return a;let d=new RegExp(_);for(;(o=d.exec(e))!==null;)if(a.push({ruleId:"PSS003",category:"SMUGGLING",severity:"LOW",message:"Detected hidden Markdown comment.",loc:p(o.index,n),offendingText:o[0],readableLabel:"[Hidden Comment]",suggestion:"Comments are not visible in rendered Markdown but can carry instructions.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS003"}),r.stopOnFirstThreat)return a;let i=new RegExp(k);for(;(o=i.exec(e))!==null;)if(a.push({ruleId:"PSS004",category:"SMUGGLING",severity:"LOW",message:"Detected empty Markdown link (invisible in rendered output).",loc:p(o.index,n),offendingText:o[0],readableLabel:"[Empty Link]",suggestion:"Empty links can be used to hide URLs or data.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS004"}),r.stopOnFirstThreat)return a;return a};var K={"\u202A":"PUSH","\u202B":"PUSH","\u202D":"PUSH","\u202E":"PUSH","\u2066":"PUSH","\u2067":"PUSH","\u2068":"PUSH","\u202C":"POP","\u2069":"POP"},E=(e,r={},n={})=>{let a=[];n.lineOffsets=n.lineOffsets??u(e);let o=e.split(`
`),t=0;for(let s=0;s<o.length;s++){let d=o[s],i=null;for(let l=0;l<d.length;l++){let c=d[l],g=K[c];if(g==="PUSH"&&i===null)i=t;else if(g==="POP"&&i!==null){let f=t+1,m=e.slice(i,f),S=e.slice(i+1,t);if(a.push({ruleId:"PST001",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Bidirectional override characters detected (Trojan Source). These characters can visually reorder text and mislead readers.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST001",loc:p(i,n),offendingText:m,decodedPayload:S,readableLabel:"[BIDI_OVERRIDE]",suggestion:"Remove bidirectional control characters from the source."}),i=null,r.stopOnFirstThreat)return a}t++}if(i!==null){let l=t,c=e.slice(i,l),g=e.slice(i+1,l);if(a.push({ruleId:"PST002",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Unterminated bidirectional override sequence detected (Trojan Source). This may cause visual and logical text order to differ.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST002",loc:p(i,n),offendingText:c,decodedPayload:g,readableLabel:"[BIDI_UNTERMINATED]",suggestion:"Remove BIDI control characters or ensure they are properly terminated within the same line."}),r.stopOnFirstThreat)return a}t++}return a};var me=(e,r={},n={})=>{let a=H.now(),o=[],t=[];r.disableTrojan||t.push(E),r.disableInvisible||t.push(O),r.disableHomoglyphs||t.push(v),r.disableNormalization||t.push(L),r.disableSmuggling||t.push(P),r.disableInjectionPatterns||t.push(R);for(let d of t){let i=d(e,r,n);if(o.push(...i),r.stopOnFirstThreat&&i.length>0)break}let s=H.now();return{threats:o,stats:{durationMs:s-a,totalChars:e.length},isClean:o.length===0}};export{X as SEVERITY_MAP,U as ThreatCategory,G as decodeUnicodeTags,me as scan,v as scanHomoglyphs,R as scanInjectionPatterns,O as scanInvisibleChars,L as scanNormalization,P as scanSmuggling,E as scanTrojanSource};
var I={CRITICAL:1,HIGH:2,MEDIUM:3,LOW:4},T=(i=>(i.Invisible="INVISIBLE_CHAR",i.Homoglyph="HOMOGLYPH",i.Smuggling="SMUGGLING",i.Injection="PROMPT_INJECTION",i.Trojan="TROJAN_SOURCE",i.Normalization="NORMALIZATION",i))(T||{});var j=/\p{Script=Latin}/u,w=/\p{Script=Cyrillic}/u,F=/\p{Script=Greek}/u,M=/[\p{L}\p{N}_]+/gu,x=(s,t={})=>{let r=new RegExp(M),n=r.exec(s);if(!n)return[];let d=[];for(;n!==null;){let e=n[0],i=n.index,u=j.test(e),g=w.test(e),a=F.test(e);if(u&&(g||a)){let o=[];u&&o.push("Latin"),g&&o.push("Cyrillic"),a&&o.push("Greek");let c=i,l=c+e.length;if(d.push({ruleId:"PSH001",category:"HOMOGLYPH",severity:"CRITICAL",message:`Mixed-script homoglyph detected: "${e}" (${o.join(" + ")})`,referenceUrl:"https://promptshield.js.org/docs/detectors/homoglyph#PSH001",range:{start:c,end:l},offendingText:e,readableLabel:`[Mixed-Script] ${e}`,suggestion:"Replace visually similar characters with characters from a single script."}),t?.stopOnFirstThreat&&!t.ignoreChecker?.(i,l))return d}n=r.exec(s)}return d};var W=[{id:"PSI001",type:"override",severity:"CRITICAL",message:"Prompt injection attempt: ignore previous instructions",regex:/ignore\s+(all\s+)?previous\s+instructions/gi,normalizedPattern:"ignorepreviousinstructions"},{id:"PSI002",type:"exfiltration",severity:"CRITICAL",message:"Attempt to reveal system prompt",regex:/(reveal|show|display|print)\s+(the\s+)?(system|hidden)\s+prompt/gi,normalizedPattern:"revealsystemprompt"},{id:"PSI003",type:"guardrail-bypass",severity:"HIGH",message:"Attempt to disable guardrails or safety protections",regex:/disable\s+(the\s+)?(guardrails|safety|safeguards)/gi,normalizedPattern:"disableguardrails"},{id:"PSI004",type:"override",severity:"HIGH",message:"System instruction override detected",regex:/override\s+(the\s+)?(system\s+)?(instructions|rules)/gi,normalizedPattern:"overridesysteminstructions"},{id:"PSI005",type:"override",severity:"CRITICAL",message:"Prompt injection attempt: ignore system prompt",regex:/ignore\s+(the\s+)?system\s+prompt/gi,normalizedPattern:"ignoresystemprompt"},{id:"PSI006",type:"override",severity:"CRITICAL",message:"Instruction override: follow attacker-provided instructions",regex:/follow\s+(my|these)\s+instructions/gi,normalizedPattern:"followmyinstructions"},{id:"PSI007",type:"role-override",severity:"HIGH",message:"Role override instruction detected",regex:/(you\s+are\s+now|act\s+as)\s+/gi,normalizedPattern:"youarenow"},{id:"PSI008",type:"exfiltration",severity:"CRITICAL",message:"Attempt to reveal hidden instructions",regex:/(reveal|show|display)\s+(hidden|internal)\s+instructions/gi,normalizedPattern:"revealhiddeninstructions"}],D=s=>{let t=[],r=[];for(let n=0;n<s.length;n++){let d=s[n].normalize("NFKD").replace(/\p{M}+/gu,"").toLowerCase();for(let e of d)/[a-z]/.test(e)&&(t.push(e),r.push(n))}return{normalized:t.join(""),map:r}},R=(s,t={})=>{let r=[],{normalized:n,map:d}=D(s);for(let e of W){let i=[],u=new RegExp(e.regex),g;for(;(g=u.exec(s))!==null;){let l=g.index,h=l+g[0].length;if(i.push({ruleId:e.id,category:"PROMPT_INJECTION",severity:e.severity,message:`\`${e.type}\`: ${e.message}`,offendingText:g[0],range:{start:l,end:h},readableLabel:`[Injection (${e.type})]`,suggestion:"Remove instruction-override language from prompts or user content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${e.id}`}),t.stopOnFirstThreat&&I[e.severity]<=I[t.minSeverity??"LOW"]&&!t.ignoreChecker?.(l,h))return r.push(...i),r;g[0].length===0&&u.lastIndex++}let a=e.normalizedPattern.length,o=n.indexOf(e.normalizedPattern),c=0;for(;o!==-1;){let l=d[o],h=d[o+a-1]+1;for(;c<i.length&&i[c].range.end<l;)c++;let p=!1;for(let m=c;m<i.length;m++){let y=i[m];if(y.range.start>h)break;if(l===y.range.start&&h===y.range.end){p=!0;break}}if(!p){let m=s.slice(l,h);if(i.push({ruleId:e.id,category:"PROMPT_INJECTION",severity:e.severity,message:`\`${e.type}\`: ${e.message} (obfuscated form detected)`,offendingText:m,range:{start:l,end:h},readableLabel:`[Injection (${e.type})]`,suggestion:"Obfuscated instruction detected. Inspect and remove malicious content.",referenceUrl:`https://promptshield.js.org/docs/detectors/injection-patterns#${e.id}`}),t.stopOnFirstThreat&&I[e.severity]<=I[t.minSeverity??"LOW"]&&!t.ignoreChecker?.(l,h))return r.push(...i),r}o=n.indexOf(e.normalizedPattern,o+1)}r.push(...i)}return r};var v={"\u200B":"ZWSP","\u200C":"ZWNJ","\u200D":"ZWJ","\u2060":"WJ","\u180E":"MVS","\uFEFF":"BOM",\u3164:"HF",\uFFA0:"HHF"},N=/([\u200B-\u200D\u2060\u180E\uFEFF\u3164\uFFA0]|\uDB40[\uDC00-\uDC7F])/gu,k=16,L=(s,t={})=>{let r=new RegExp(N),n=r.exec(s);if(!n||t?.minSeverity==="CRITICAL")return[];let d=[],e=-1,i=-1,u=()=>{e=-1,i=-1},g=()=>{if(e===-1)return;let a=s.slice(e,i),o=z(a),c=[...a].map(h=>{let p=h.codePointAt(0);return v[h]||`U+${p?.toString(16).toUpperCase()}`}),l;if(o?l={ruleId:"PSU004",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Unicode tag characters encode hidden ASCII content inside invisible text.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU004",range:{start:e,end:i},offendingText:a,decodedPayload:o,readableLabel:"[TAG_PAYLOAD]",suggestion:"Remove Unicode tag characters containing hidden text."}:t.minSeverity!=="HIGH"&&a.length>=k?l={ruleId:"PSU005",category:"INVISIBLE_CHAR",severity:"MEDIUM",message:"Excessive invisible characters detected. Large invisible sequences are commonly used for padding or obfuscation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU005",range:{start:e,end:i},offendingText:a,readableLabel:`[${c.join(" ")}]`,suggestion:"Remove unnecessary invisible characters."}:t.minSeverity!=="MEDIUM"&&(l={ruleId:"PSU001",category:"INVISIBLE_CHAR",severity:"LOW",message:"Invisible Unicode characters detected. These characters can alter tokenization and prompt interpretation without being visible.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU001",range:{start:e,end:i},offendingText:a,readableLabel:`[${c.join(" ")}]`,suggestion:"Remove invisible characters to ensure the prompt text is interpreted exactly as written."}),l&&(d.push(l),t.stopOnFirstThreat&&!t.ignoreChecker?.(l.range.start,l.range.end)))throw d;u()};try{for(;n!==null;){let a=n.index,o=n[0];if(a>0&&a<s.length-1){let c=s[a-1],l=s[a+o.length];if(c?.trim()&&l?.trim()){let h=a,p=a+o.length,m={ruleId:"PSU002",category:"INVISIBLE_CHAR",severity:"HIGH",message:"Invisible character detected inside a visible token. This can manipulate token boundaries or bypass validation.",referenceUrl:"https://promptshield.js.org/docs/detectors/invisible-chars#PSU002",range:{start:h,end:p},offendingText:o,readableLabel:`[${v[o]}]`||"[INVISIBLE]",suggestion:"Remove invisible characters embedded within words."};if(d.push(m),t.stopOnFirstThreat&&!t.ignoreChecker?.(h,p))return d}}e===-1?(e=a,i=a+o.length):a===i?i+=o.length:(g(),e=a,i=a+o.length),n=r.exec(s)}g()}catch(a){return a}return d},z=s=>{let t="",r=!1;for(let n of s){let d=n.codePointAt(0);if(d>=917504&&d<=917631){let e=d-917504;e>=32&&e<=126&&(t+=String.fromCharCode(e),r=!0)}}return r?t:void 0};var C=(s,t={})=>{if(t.minSeverity==="CRITICAL")return[];let r=[],n=s.normalize("NFKC");if(s===n)return[];let d=0,e=-1,i=-1,u=()=>{if(e===-1)return;let g=s.slice(e,i),a=g.normalize("NFKC"),o=/^[a-z0-9\s]+$/i.test(a),c=o?"PSN001":"PSN002",l=o?"LOW":"MEDIUM";r.push({ruleId:c,category:"NORMALIZATION",severity:l,message:"Text changes under Unicode NFKC normalization. This may cause ambiguity between displayed and interpreted content.",referenceUrl:`https://promptshield.js.org/docs/detectors/normalization#${c}`,range:{start:e,end:i},offendingText:g,decodedPayload:a,readableLabel:"[NFKC_DIFF]",suggestion:"Replace with normalized text to avoid ambiguity."}),e=-1,i=-1};for(let g of s){let a=g.normalize("NFKC");if(g!==a){if(e===-1?(e=d,i=d+g.length):i+=g.length,t.stopOnFirstThreat&&!t.ignoreChecker?.(d,d+g.length))return u(),r}else u();d+=g.length}return u(),r};var P=24,E=4096,_=/(?:[A-Za-z0-9+/_-]{4}[\s\u200B-\u200D\u2060\uFEFF]*){8,}(?:[A-Za-z0-9+/_-]{2}==|[A-Za-z0-9+/_-]{3}=)?/g,G=/\b(?:[0-9a-fA-F]{2}){12,}\b/g,B=/<!--[\s\S]*?-->/g,$=/\[\s*\]\([^)]+\)/g,K=/<(details|template)\b[^><]{0,200}>[\s\S]{0,100000}?<\/\1>/gi,X=/<summary\b[^><]{0,200}>[\s\S]{0,20000}?<\/summary>/i,V=/([\u200B-\u200D\u2060\uFEFF\u3164\uFFA0]+)/g,Y=s=>{if(typeof Buffer<"u")return Buffer.from(s,"base64").toString("utf8");let t=atob(s),r=Uint8Array.from(t,n=>n.charCodeAt(0));return new TextDecoder().decode(r)},Z=s=>{try{let t=s.replace(/\s+/g,"");if(t.length<P||t.length>E)return null;let r=Y(t);if(!r)return null;let n=0;for(let e of r){let i=e.charCodeAt(0);i>=32&&i<=126&&n++}return n/r.length>=.7?r:null}catch{return null}},q=s=>{try{if(s.length<P)return null;let t=new Uint8Array(s.length/2);for(let e=0;e<t.length;e++)t[e]=parseInt(s.slice(e*2,e*2+2),16);let r=new TextDecoder().decode(t),n=0;for(let e of r){let i=e.charCodeAt(0);i>=32&&i<=126&&n++}return n/r.length>=.7?r:null}catch{return null}},O=(s,t={})=>{if(t.minSeverity==="CRITICAL")return[];let r=[],n,d=new RegExp(V);for(;(n=d.exec(s))!==null;){let o=n[0];if(o.length<8||o.length>E)continue;let c=Array.from(new Set([...o]));if(c.length<2||c.length>4)continue;let l=[];for(let h=0;h<c.length;h++)for(let p=0;p<c.length;p++)h!==p&&l.push({zero:c[h],one:c[p]});for(let{zero:h,one:p}of l){let m="";for(let f of o)f===h?m+="0":f===p&&(m+="1");let y="";for(let f=0;f<m.length;f+=8){let b=m.slice(f,f+8);if(b.length!==8)continue;let S=parseInt(b,2);S>=32&&S<=126&&(y+=String.fromCharCode(S))}if(y.length>=3){let f=n.index,b=f+o.length;if(r.push({ruleId:"PSS001",category:"SMUGGLING",severity:"HIGH",message:"Detected hidden steganography message encoded in invisible characters.",range:{start:f,end:b},offendingText:o,decodedPayload:y,readableLabel:`[Hidden]: ${y.slice(0,120)}...`,suggestion:"Invisible-character encoding detected. Inspect hidden content.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS001"}),t.stopOnFirstThreat&&!t.ignoreChecker?.(f,b))return r;break}}}if(t.minSeverity==="HIGH")return r;let e=new RegExp(_);for(;(n=e.exec(s))!==null;){let o=n[0],c=Z(o);if(!c)continue;let l=n.index,h=l+o.length;if(r.push({ruleId:"PSS002",category:"SMUGGLING",severity:"MEDIUM",message:"Detected Base64 payload containing readable content.",range:{start:l,end:h},offendingText:o,decodedPayload:c,readableLabel:`[Base64]: ${c.slice(0,120)}...`,suggestion:"Decoded Base64 contains readable text. Inspect payload.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS002"}),t.stopOnFirstThreat&&!t.ignoreChecker?.(l,h))return r}let i=new RegExp(G);for(;(n=i.exec(s))!==null;){let o=n[0],c=q(o);if(!c)continue;let l=n.index,h=l+o.length;if(r.push({ruleId:"PSS006",category:"SMUGGLING",severity:"MEDIUM",message:"Detected hex-encoded payload containing readable content.",range:{start:l,end:h},offendingText:o,decodedPayload:c,readableLabel:`[HEX]: ${c.slice(0,120)}...`,suggestion:"Decoded hex contains readable text. Inspect payload.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS006"}),t.stopOnFirstThreat&&!t.ignoreChecker?.(l,h))return r}if(t.minSeverity==="MEDIUM")return r;let u=new RegExp(B);for(;(n=u.exec(s))!==null;){let o=n.index,c=o+n[0].length;if(r.push({ruleId:"PSS003",category:"SMUGGLING",severity:"LOW",message:"Detected hidden Markdown comment.",range:{start:o,end:c},offendingText:n[0],readableLabel:"[Hidden Comment]",suggestion:"Comments are not visible in rendered Markdown but can carry instructions.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS003"}),t.stopOnFirstThreat&&!t.ignoreChecker?.(o,c))return r}let g=new RegExp($);for(;(n=g.exec(s))!==null;){let o=n.index,c=o+n[0].length;if(r.push({ruleId:"PSS004",category:"SMUGGLING",severity:"LOW",message:"Detected empty Markdown link (invisible in rendered output).",range:{start:o,end:c},offendingText:n[0],readableLabel:"[Empty Link]",suggestion:"Empty links can be used to hide URLs or data.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS004"}),t.stopOnFirstThreat&&!t.ignoreChecker?.(o,c))return r}let a=new RegExp(K);for(;(n=a.exec(s))!==null;){let o=n[0];if(/^<details/i.test(o)&&X.test(o))continue;let c=n.index,l=c+o.length;if(r.push({ruleId:"PSS005",category:"SMUGGLING",severity:"LOW",message:"Detected hidden HTML container potentially concealing content.",range:{start:c,end:l},offendingText:o,readableLabel:"[Hidden HTML]",suggestion:"Hidden containers may conceal instructions from rendered output.",referenceUrl:"https://promptshield.js.org/docs/detectors/smuggling#PSS005"}),t.stopOnFirstThreat&&!t.ignoreChecker?.(c,l))return r}return r};var J={"\u202A":"PUSH","\u202B":"PUSH","\u202D":"PUSH","\u202E":"PUSH","\u2066":"PUSH","\u2067":"PUSH","\u2068":"PUSH","\u202C":"POP","\u2069":"POP","\u200E":"MARK","\u200F":"MARK","\u061C":"MARK"},A=(s,t={})=>{let r=[],n=[],d=0;for(let e=0;e<s.length;){let i=s.codePointAt(e),u=String.fromCodePoint(i),g=J[u];if(g==="PUSH")n.push(d);else if(g==="POP"&&n.length){let a=n.pop(),o=d+u.length,c=s.slice(a,o),l=s.slice(a+1,o-1);if(r.push({ruleId:"PST001",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Bidirectional override characters detected (Trojan Source). These characters can visually reorder text and mislead reviewers.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST001",range:{start:a,end:o},offendingText:c,decodedPayload:l,readableLabel:"[BIDI_OVERRIDE]",suggestion:"Remove bidirectional control characters or replace them with visible equivalents."}),t.stopOnFirstThreat&&!t.ignoreChecker?.(a,o))return r}if((u===`
`||u==="\r")&&n.length){let a=n.pop(),o=d,c=s.slice(a,o),l=s.slice(a+1,o);if(r.push({ruleId:"PST002",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Unterminated bidirectional override sequence detected (Trojan Source). This may cause visual and logical text order to differ.",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST002",range:{start:a,end:o},offendingText:c,decodedPayload:l,readableLabel:"[BIDI_UNTERMINATED]",suggestion:"Ensure BIDI control characters are properly terminated within the same logical line or remove them entirely."}),t.stopOnFirstThreat&&!t.ignoreChecker?.(a,o))return r}d+=u.length,e+=u.length}for(;n.length;){let e=n.pop(),i=s.length,u=s.slice(e,i),g=s.slice(e+1);if(r.push({ruleId:"PST002",category:"TROJAN_SOURCE",severity:"CRITICAL",message:"Unterminated bidirectional override sequence detected (Trojan Source).",referenceUrl:"https://promptshield.js.org/docs/detectors/trojan-source#PST002",range:{start:e,end:i},offendingText:u,decodedPayload:g,readableLabel:"[BIDI_UNTERMINATED]",suggestion:"Remove or terminate bidirectional override characters before the end of the document."}),t.stopOnFirstThreat&&!t.ignoreChecker?.(e,i))return r}return r};var Q=s=>{let t=[0];for(let r=0;r<s.length;r++)s[r]===`
`&&t.push(r+1);return t},H=(s,t)=>{let{lineOffsets:r,baseLine:n,baseCol:d}=t,e=0,i=r.length-1;for(;e<=i;){let g=e+i>>1;r[g]<=s?e=g+1:i=g-1}let u=Math.max(i,0);return{line:n+u,column:s-r[u]+(u===0?d:1),index:s}},U=(s,t,r={})=>{let n=Q(t),{baseLine:d=1,baseCol:e=1}=r,i={lineOffsets:n,baseLine:d,baseCol:e};return s.map(u=>{let g=H(u.range.start,i),a=H(u.range.end,i);return{...u,range:{start:g,end:a}}})};var fe=(s,t={})=>{let r=[],n=[];t.disableTrojan||n.push(A),t.disableInvisible||n.push(L),t.disableHomoglyphs||n.push(x),t.disableNormalization||n.push(C),t.disableSmuggling||n.push(O),t.disableInjectionPatterns||n.push(R);for(let d of n){let e=d(s,t);if(r.push(...e),t.stopOnFirstThreat&&e.length>0)break}return{threats:U(r,s),isClean:r.length===0}};export{I as SEVERITY_MAP,T as ThreatCategory,z as decodeUnicodeTags,U as enrichWithLocation,Q as getLineOffsets,H as getLocForIndex,fe as scan,x as scanHomoglyphs,R as scanInjectionPatterns,L as scanInvisibleChars,C as scanNormalization,O as scanSmuggling,A as scanTrojanSource};

@@ -5,3 +5,3 @@ {

"private": false,
"version": "0.1.0",
"version": "1.0.0",
"description": "The heart of the PromptShield ecosystem. A zero-dependency, isomorphic TypeScript engine for detecting invisible characters, BIDI overrides, and homoglyph attacks in AI prompts.",

@@ -45,3 +45,4 @@ "license": "MIT",

],
"icon": "Shield"
"icon": "Shield",
"description": "High-performance threat detector"
},

@@ -48,0 +49,0 @@ "funding": [