@ai-pip/csl 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/package.json +10 -28
  2. package/src/index.test.ts +429 -0
  3. package/{index.ts → src/index.ts} +53 -50
  4. package/src/test-external.js +547 -0
  5. package/layers/csl/adapters/index.ts +0 -9
  6. package/layers/csl/adapters/input/DOMAdapter.ts +0 -236
  7. package/layers/csl/adapters/input/UIAdapter.ts +0 -0
  8. package/layers/csl/adapters/output/ConsoleLogger.ts +0 -34
  9. package/layers/csl/adapters/output/CryptoHashGenerator.ts +0 -29
  10. package/layers/csl/adapters/output/FilePolicyRepository.ts +0 -0
  11. package/layers/csl/adapters/output/InMemoryPolicyRepository.ts +0 -135
  12. package/layers/csl/adapters/output/SystemTimestampProvider.ts +0 -9
  13. package/layers/csl/domain/entities/CSLResult.ts +0 -309
  14. package/layers/csl/domain/entities/Segment.ts +0 -338
  15. package/layers/csl/domain/entities/index.ts +0 -2
  16. package/layers/csl/domain/exceptions/ClassificationError.ts +0 -26
  17. package/layers/csl/domain/exceptions/SegmentationError.ts +0 -30
  18. package/layers/csl/domain/exceptions/index.ts +0 -2
  19. package/layers/csl/domain/index.ts +0 -4
  20. package/layers/csl/domain/services/AnomalyService.ts +0 -255
  21. package/layers/csl/domain/services/LineageService.ts +0 -224
  22. package/layers/csl/domain/services/NormalizationService.ts +0 -392
  23. package/layers/csl/domain/services/OriginClassificationService.ts +0 -69
  24. package/layers/csl/domain/services/PiDetectionService.ts +0 -475
  25. package/layers/csl/domain/services/PolicyService.ts +0 -296
  26. package/layers/csl/domain/services/SegmentClassificationService.ts +0 -105
  27. package/layers/csl/domain/services/SerializationService.ts +0 -229
  28. package/layers/csl/domain/services/index.ts +0 -7
  29. package/layers/csl/domain/value-objects/AnomalyScore.ts +0 -23
  30. package/layers/csl/domain/value-objects/ContentHash.ts +0 -54
  31. package/layers/csl/domain/value-objects/LineageEntry.ts +0 -42
  32. package/layers/csl/domain/value-objects/Origin-map.ts +0 -67
  33. package/layers/csl/domain/value-objects/Origin.ts +0 -99
  34. package/layers/csl/domain/value-objects/Pattern.ts +0 -221
  35. package/layers/csl/domain/value-objects/PiDetection.ts +0 -140
  36. package/layers/csl/domain/value-objects/PiDetectionResult.ts +0 -275
  37. package/layers/csl/domain/value-objects/PolicyRule.ts +0 -151
  38. package/layers/csl/domain/value-objects/TrustLevel.ts +0 -34
  39. package/layers/csl/domain/value-objects/index.ts +0 -10
  40. package/layers/csl/index.ts +0 -3
  41. package/layers/csl/ports/index.ts +0 -10
  42. package/layers/csl/ports/input/ClassificationPort.ts +0 -76
  43. package/layers/csl/ports/input/SegmentationPort.ts +0 -81
  44. package/layers/csl/ports/output/DOMAdapter.ts +0 -14
  45. package/layers/csl/ports/output/HashGenerator.ts +0 -18
  46. package/layers/csl/ports/output/Logger.ts +0 -17
  47. package/layers/csl/ports/output/PolicyRepository.ts +0 -29
  48. package/layers/csl/ports/output/SegmentClassified.ts +0 -8
  49. package/layers/csl/ports/output/TimeStampProvider.ts +0 -5
  50. package/layers/csl/services/CSLService.ts +0 -393
  51. package/layers/csl/services/index.ts +0 -1
  52. package/layers/csl/types/entities-types.ts +0 -37
  53. package/layers/csl/types/index.ts +0 -4
  54. package/layers/csl/types/pi-types.ts +0 -111
  55. package/layers/csl/types/port-output-types.ts +0 -17
  56. package/layers/csl/types/value-objects-types.ts +0 -213
  57. package/layers/csl/utils/colors.ts +0 -25
  58. package/layers/csl/utils/pattern-helpers.ts +0 -174
@@ -1,2 +0,0 @@
1
- export * from './ClassificationError';
2
- export * from './SegmentationError';
@@ -1,4 +0,0 @@
1
- export * from './entities'
2
- export * from './services'
3
- export * from './value-objects'
4
- export * from './exceptions'
@@ -1,255 +0,0 @@
1
- import type { Segment } from '../entities'
2
- import { AnomalyScore } from '../value-objects'
3
- import type { AnomalyAction, RiskScore } from '../../types'
4
- import { TrustLevelType } from '../../types'
5
-
6
- /**
7
- * Configuration for AnomalyService
8
- *
9
- * @property highRiskThreshold - Score threshold for BLOCK action (default: 0.7)
10
- * @property mediumRiskThreshold - Score threshold for WARN action (default: 0.3)
11
- * @property trustLevelWeight - Weight multiplier for trust level in score calculation (default: 0.3)
12
- * @property piDetectionWeight - Weight multiplier for PI detection in score calculation (default: 0.5)
13
- * @property contentLengthWeight - Weight multiplier for content length anomaly (default: 0.1)
14
- * @property originWeight - Weight multiplier for origin risk in score calculation (default: 0.1)
15
- */
16
- export interface AnomalyServiceConfig {
17
- readonly highRiskThreshold?: RiskScore
18
- readonly mediumRiskThreshold?: RiskScore
19
- readonly trustLevelWeight?: number
20
- readonly piDetectionWeight?: number
21
- readonly contentLengthWeight?: number
22
- readonly originWeight?: number
23
- }
24
-
25
- /**
26
- * AnomalyService calculates anomaly scores for content segments based on multiple risk factors.
27
- *
28
- * @remarks
29
- * This service performs complex risk analysis by combining multiple factors:
30
- * - Trust level of the segment
31
- * - Prompt injection detection results
32
- * - Content length anomalies
33
- * - Origin risk assessment
34
- *
35
- * **Key Features:**
36
- * - Configurable thresholds and weights
37
- * - Multi-factor risk analysis
38
- * - Deterministic scoring algorithm
39
- * - Returns AnomalyScore with recommended action
40
- *
41
- * **Scoring Algorithm:**
42
- * The final score is a weighted combination of:
43
- * 1. Trust Level Risk (0-1): Lower trust = higher risk
44
- * 2. PI Detection Risk (0-1): Based on detection score
45
- * 3. Content Length Anomaly (0-1): Very short or very long content
46
- * 4. Origin Risk (0-1): Based on origin type
47
- *
48
- * Final score = (trustLevelRisk * trustLevelWeight) +
49
- * (piDetectionRisk * piDetectionWeight) +
50
- * (contentLengthRisk * contentLengthWeight) +
51
- * (originRisk * originWeight)
52
- *
53
- * @example
54
- * ```typescript
55
- * // Default configuration
56
- * const anomalyService = new AnomalyService()
57
- *
58
- * // Custom configuration
59
- * const customService = new AnomalyService({
60
- * highRiskThreshold: 0.8,
61
- * mediumRiskThreshold: 0.4,
62
- * piDetectionWeight: 0.7
63
- * })
64
- *
65
- * // Calculate anomaly score
66
- * const anomalyScore = anomalyService.calculateScore(segment)
67
- *
68
- * if (anomalyScore.isHighRisk()) {
69
- * console.log('High risk detected!')
70
- * }
71
- * ```
72
- */
73
- export class AnomalyService {
74
- private readonly config: Required<AnomalyServiceConfig>
75
-
76
- constructor(config?: AnomalyServiceConfig) {
77
- this.config = {
78
- highRiskThreshold: config?.highRiskThreshold ?? 0.7,
79
- mediumRiskThreshold: config?.mediumRiskThreshold ?? 0.3,
80
- trustLevelWeight: config?.trustLevelWeight ?? 0.3,
81
- piDetectionWeight: config?.piDetectionWeight ?? 0.5,
82
- contentLengthWeight: config?.contentLengthWeight ?? 0.1,
83
- originWeight: config?.originWeight ?? 0.1,
84
- }
85
-
86
- // Validate thresholds
87
- if (this.config.highRiskThreshold < this.config.mediumRiskThreshold) {
88
- throw new Error(
89
- `AnomalyService: highRiskThreshold (${this.config.highRiskThreshold}) ` +
90
- `must be >= mediumRiskThreshold (${this.config.mediumRiskThreshold})`
91
- )
92
- }
93
-
94
- // Validate weights sum to approximately 1.0 (allow small floating point differences)
95
- const totalWeight = this.config.trustLevelWeight +
96
- this.config.piDetectionWeight +
97
- this.config.contentLengthWeight +
98
- this.config.originWeight
99
-
100
- if (Math.abs(totalWeight - 1) > 0.01) {
101
- throw new Error(
102
- `AnomalyService: weights must sum to 1.0, got ${totalWeight}`
103
- )
104
- }
105
- }
106
-
107
- /**
108
- * Calculates the anomaly score for a segment based on multiple risk factors
109
- *
110
- * @param segment - The segment to analyze
111
- * @returns AnomalyScore with calculated score and recommended action
112
- *
113
- * @throws {TypeError} If segment is not a Segment instance
114
- *
115
- * @example
116
- * ```typescript
117
- * const anomalyScore = anomalyService.calculateScore(segment)
118
- * console.log(`Anomaly score: ${anomalyScore.score}, Action: ${anomalyScore.action}`)
119
- * ```
120
- */
121
- calculateScore(segment: Segment): AnomalyScore {
122
- if (!segment || typeof segment !== 'object' || !('id' in segment)) {
123
- throw new TypeError('AnomalyService.calculateScore: segment must be a Segment instance')
124
- }
125
-
126
- // Calculate individual risk components
127
- const trustLevelRisk = this.calculateTrustLevelRisk(segment)
128
- const piDetectionRisk = this.calculatePiDetectionRisk(segment)
129
- const contentLengthRisk = this.calculateContentLengthRisk(segment)
130
- const originRisk = this.calculateOriginRisk(segment)
131
-
132
- // Weighted combination
133
- const finalScore = Math.min(1,
134
- (trustLevelRisk * this.config.trustLevelWeight) +
135
- (piDetectionRisk * this.config.piDetectionWeight) +
136
- (contentLengthRisk * this.config.contentLengthWeight) +
137
- (originRisk * this.config.originWeight)
138
- )
139
-
140
- // Determine action based on thresholds
141
- const action = this.determineAction(finalScore)
142
-
143
- return new AnomalyScore(finalScore, action)
144
- }
145
-
146
- /**
147
- * Calculates risk based on trust level
148
- * TC = 0.0 (no risk), STC = 0.5 (medium risk), UC = 1.0 (high risk)
149
- */
150
- private calculateTrustLevelRisk(segment: Segment): RiskScore {
151
- const trustLevel = segment.trustLevel
152
- if (!trustLevel) {
153
- return 1 as RiskScore // Unknown trust level = maximum risk
154
- }
155
-
156
- switch (trustLevel.value) {
157
- case TrustLevelType.TC:
158
- return 0 as RiskScore
159
- case TrustLevelType.STC:
160
- return 0.5 as RiskScore
161
- case TrustLevelType.UC:
162
- return 1 as RiskScore
163
- default:
164
- return 1 as RiskScore
165
- }
166
- }
167
-
168
- /**
169
- * Calculates risk based on prompt injection detection results
170
- */
171
- private calculatePiDetectionRisk(segment: Segment): RiskScore {
172
- const piDetection = segment.piDetection
173
- if (!piDetection) {
174
- return 0 as RiskScore // No detection = no risk from this factor
175
- }
176
-
177
- // Use the detection score directly
178
- return piDetection.score
179
- }
180
-
181
- /**
182
- * Calculates risk based on content length anomalies
183
- * Very short (< 10 chars) or very long (> 10000 chars) content is suspicious
184
- */
185
- private calculateContentLengthRisk(segment: Segment): RiskScore {
186
- const contentLength = segment.content.length
187
-
188
- // Very short content (potential injection fragments)
189
- if (contentLength < 10) {
190
- return 0.8 as RiskScore
191
- }
192
-
193
- // Very long content (potential obfuscation)
194
- if (contentLength > 10000) {
195
- return 0.6 as RiskScore
196
- }
197
-
198
- // Normal length range
199
- if (contentLength >= 10 && contentLength <= 1000) {
200
- return 0 as RiskScore
201
- }
202
-
203
- // Medium length (1000-10000) - slight risk
204
- return 0.2 as RiskScore
205
- }
206
-
207
- /**
208
- * Calculates risk based on origin type
209
- */
210
- private calculateOriginRisk(segment: Segment): RiskScore {
211
- const origin = segment.origin
212
-
213
- // User input is always high risk
214
- if (origin.isUser()) {
215
- return 1 as RiskScore
216
- }
217
-
218
- // External content (network, injected) is high risk
219
- if (origin.isExternal()) {
220
- return 0.9 as RiskScore
221
- }
222
-
223
- // System content is low risk
224
- if (origin.isSystem()) {
225
- return 0 as RiskScore
226
- }
227
-
228
- // DOM content risk depends on visibility
229
- if (origin.isDom()) {
230
- // Hidden DOM content is more risky
231
- return origin.type === 'DOM_HIDDEN' ? 0.7 as RiskScore : 0.3 as RiskScore
232
- }
233
-
234
- // Unknown origin is high risk
235
- if (origin.isUnknown()) {
236
- return 1 as RiskScore
237
- }
238
-
239
- // Default: medium risk
240
- return 0.5 as RiskScore
241
- }
242
-
243
- /**
244
- * Determines the action based on the calculated score
245
- */
246
- private determineAction(score: RiskScore): AnomalyAction {
247
- if (score >= this.config.highRiskThreshold) {
248
- return 'BLOCK'
249
- } else if (score >= this.config.mediumRiskThreshold) {
250
- return 'WARN'
251
- } else {
252
- return 'ALLOW'
253
- }
254
- }
255
- }
@@ -1,224 +0,0 @@
1
- import { LineageEntry } from '../value-objects'
2
-
3
- /**
4
- * LineageService manages the lineage (audit trail) of content segments.
5
- *
6
- * @remarks
7
- * This service maintains a stateful map of segment IDs to their lineage entries,
8
- * allowing complete traceability of how each segment was processed through the pipeline.
9
- *
10
- * **Key Features:**
11
- * - Maintains internal state (Map) for lineage tracking
12
- * - Supports multiple entries per segment (chronological order)
13
- * - Provides query methods for lineage retrieval
14
- * - Thread-safe operations (immutable entries)
15
- *
16
- * **Usage:**
17
- * LineageService is instantiated once and reused across the processing pipeline.
18
- * Each processing step should add an entry to track what happened to the segment.
19
- *
20
- * @example
21
- * ```typescript
22
- * const lineageService = new LineageService()
23
- *
24
- * // Add entries during processing
25
- * lineageService.addEntry('seg-123', new LineageEntry('normalization', Date.now(), 'Unicode normalized'))
26
- * lineageService.addEntry('seg-123', new LineageEntry('classification', Date.now(), 'Classified as TC'))
27
- *
28
- * // Retrieve lineage
29
- * const lineage = lineageService.getLineage('seg-123')
30
- * console.log(`Segment has ${lineage.length} processing steps`)
31
- *
32
- * // Clear lineage when done
33
- * lineageService.clearLineage('seg-123')
34
- * ```
35
- */
36
- export class LineageService {
37
- private readonly lineageMap: Map<string, LineageEntry[]>
38
-
39
- constructor() {
40
- this.lineageMap = new Map()
41
- }
42
-
43
- /**
44
- * Adds a lineage entry for a specific segment
45
- *
46
- * @param segmentId - The unique identifier of the segment
47
- * @param entry - The lineage entry to add
48
- *
49
- * @throws {TypeError} If segmentId is not a valid string
50
- * @throws {TypeError} If entry is not a LineageEntry instance
51
- *
52
- * @example
53
- * ```typescript
54
- * lineageService.addEntry('seg-123', new LineageEntry('normalization', Date.now(), 'Applied'))
55
- * ```
56
- */
57
- addEntry(segmentId: string, entry: LineageEntry): void {
58
- if (!segmentId || typeof segmentId !== 'string' || segmentId.trim().length === 0) {
59
- throw new TypeError('LineageService.addEntry: segmentId must be a non-empty string')
60
- }
61
-
62
- if (!(entry instanceof LineageEntry)) {
63
- throw new TypeError('LineageService.addEntry: entry must be a LineageEntry instance')
64
- }
65
-
66
- const currentLineage = this.lineageMap.get(segmentId) ?? []
67
- this.lineageMap.set(segmentId, [...currentLineage, entry])
68
- }
69
-
70
- /**
71
- * Retrieves all lineage entries for a specific segment
72
- *
73
- * @param segmentId - The unique identifier of the segment
74
- * @returns Array of LineageEntry instances in chronological order (empty array if no entries)
75
- *
76
- * @throws {TypeError} If segmentId is not a valid string
77
- *
78
- * @example
79
- * ```typescript
80
- * const lineage = lineageService.getLineage('seg-123')
81
- * lineage.forEach(entry => {
82
- * console.log(`${entry.step} at ${new Date(entry.timestamp).toISOString()}`)
83
- * })
84
- * ```
85
- */
86
- getLineage(segmentId: string): readonly LineageEntry[] {
87
- if (!segmentId || typeof segmentId !== 'string' || segmentId.trim().length === 0) {
88
- throw new TypeError('LineageService.getLineage: segmentId must be a non-empty string')
89
- }
90
-
91
- const lineage = this.lineageMap.get(segmentId)
92
- return lineage ? Object.freeze([...lineage]) : Object.freeze([])
93
- }
94
-
95
- /**
96
- * Checks if a segment has any lineage entries
97
- *
98
- * @param segmentId - The unique identifier of the segment
99
- * @returns true if the segment has lineage entries, false otherwise
100
- *
101
- * @throws {TypeError} If segmentId is not a valid string
102
- *
103
- * @example
104
- * ```typescript
105
- * if (lineageService.hasLineage('seg-123')) {
106
- * const entries = lineageService.getLineage('seg-123')
107
- * }
108
- * ```
109
- */
110
- hasLineage(segmentId: string): boolean {
111
- if (!segmentId || typeof segmentId !== 'string' || segmentId.trim().length === 0) {
112
- throw new TypeError('LineageService.hasLineage: segmentId must be a non-empty string')
113
- }
114
-
115
- const lineage = this.lineageMap.get(segmentId)
116
- return lineage !== undefined && lineage.length > 0
117
- }
118
-
119
- /**
120
- * Clears all lineage entries for a specific segment
121
- *
122
- * @param segmentId - The unique identifier of the segment
123
- * @returns true if lineage was cleared, false if segment had no lineage
124
- *
125
- * @throws {TypeError} If segmentId is not a valid string
126
- *
127
- * @example
128
- * ```typescript
129
- * const hadLineage = lineageService.clearLineage('seg-123')
130
- * if (hadLineage) {
131
- * console.log('Lineage cleared')
132
- * }
133
- * ```
134
- */
135
- clearLineage(segmentId: string): boolean {
136
- if (!segmentId || typeof segmentId !== 'string' || segmentId.trim().length === 0) {
137
- throw new TypeError('LineageService.clearLineage: segmentId must be a non-empty string')
138
- }
139
-
140
- const hadLineage = this.lineageMap.has(segmentId)
141
- this.lineageMap.delete(segmentId)
142
- return hadLineage
143
- }
144
-
145
- /**
146
- * Retrieves all segment IDs that have lineage entries
147
- *
148
- * @returns Array of segment IDs that have lineage entries
149
- *
150
- * @example
151
- * ```typescript
152
- * const segmentIds = lineageService.getAllSegmentIds()
153
- * console.log(`Tracking ${segmentIds.length} segments`)
154
- * ```
155
- */
156
- getAllSegmentIds(): readonly string[] {
157
- return Object.freeze([...this.lineageMap.keys()])
158
- }
159
-
160
- /**
161
- * Retrieves the total count of lineage entries across all segments
162
- *
163
- * @returns Total number of lineage entries
164
- *
165
- * @example
166
- * ```typescript
167
- * const totalEntries = lineageService.getTotalEntryCount()
168
- * console.log(`Total lineage entries: ${totalEntries}`)
169
- * ```
170
- */
171
- getTotalEntryCount(): number {
172
- let total = 0
173
- for (const lineage of this.lineageMap.values()) {
174
- total += lineage.length
175
- }
176
- return total
177
- }
178
-
179
- /**
180
- * Clears all lineage entries for all segments
181
- *
182
- * @returns Number of segments that had lineage entries
183
- *
184
- * @example
185
- * ```typescript
186
- * const clearedCount = lineageService.clearAllLineage()
187
- * console.log(`Cleared lineage for ${clearedCount} segments`)
188
- * ```
189
- */
190
- clearAllLineage(): number {
191
- const count = this.lineageMap.size
192
- this.lineageMap.clear()
193
- return count
194
- }
195
-
196
- /**
197
- * Gets lineage entries for a specific step type
198
- *
199
- * @param segmentId - The unique identifier of the segment
200
- * @param stepType - The step type to filter by (e.g., 'normalization', 'classification')
201
- * @returns Array of LineageEntry instances matching the step type
202
- *
203
- * @throws {TypeError} If segmentId or stepType are not valid strings
204
- *
205
- * @example
206
- * ```typescript
207
- * const normalizationEntries = lineageService.getLineageByStep('seg-123', 'normalization')
208
- * ```
209
- */
210
- getLineageByStep(segmentId: string, stepType: string): readonly LineageEntry[] {
211
- if (!segmentId || typeof segmentId !== 'string' || segmentId.trim().length === 0) {
212
- throw new TypeError('LineageService.getLineageByStep: segmentId must be a non-empty string')
213
- }
214
-
215
- if (!stepType || typeof stepType !== 'string' || stepType.trim().length === 0) {
216
- throw new TypeError('LineageService.getLineageByStep: stepType must be a non-empty string')
217
- }
218
-
219
- const lineage = this.getLineage(segmentId)
220
- return Object.freeze(
221
- lineage.filter(entry => entry.step === stepType.trim())
222
- )
223
- }
224
- }