(onProgress?: (msg: string) => void)
| 202 | let loadPromise: Promise<void> | null = null; |
| 203 | |
| 204 | export function loadTestsavant(onProgress?: (msg: string) => void): Promise<void> { |
| 205 | if (process.env.GSTACK_SECURITY_OFF === '1') { |
| 206 | testsavantState = 'failed'; |
| 207 | testsavantLoadError = 'GSTACK_SECURITY_OFF=1 — ML classifier kill switch engaged'; |
| 208 | return Promise.resolve(); |
| 209 | } |
| 210 | if (testsavantState === 'loaded') return Promise.resolve(); |
| 211 | if (loadPromise) return loadPromise; |
| 212 | testsavantState = 'loading'; |
| 213 | loadPromise = (async () => { |
| 214 | try { |
| 215 | await ensureTestsavantStaged(onProgress); |
| 216 | // Dynamic import — keeps the module boundary clean so static analyzers |
| 217 | // don't pull @huggingface/transformers into compiled contexts. |
| 218 | onProgress?.('initializing classifier'); |
| 219 | const { pipeline, env } = await import('@huggingface/transformers'); |
| 220 | env.allowLocalModels = true; |
| 221 | env.allowRemoteModels = false; |
| 222 | env.localModelPath = MODELS_DIR; |
| 223 | testsavantClassifier = await pipeline( |
| 224 | 'text-classification', |
| 225 | 'testsavant-small', |
| 226 | { dtype: 'fp32' }, |
| 227 | ); |
| 228 | // TestSavantAI's tokenizer_config.json ships with model_max_length |
| 229 | // set to a huge placeholder (1e18) which disables automatic truncation |
| 230 | // in the TextClassificationPipeline. The underlying BERT-small has |
| 231 | // max_position_embeddings: 512 — passing anything longer throws a |
| 232 | // broadcast error. Override via _tokenizerConfig (the internal source |
| 233 | // the computed model_max_length getter reads from) so the pipeline's |
| 234 | // implicit truncation: true actually kicks in. |
| 235 | const tok = testsavantClassifier?.tokenizer as any; |
| 236 | if (tok?._tokenizerConfig) { |
| 237 | tok._tokenizerConfig.model_max_length = 512; |
| 238 | } |
| 239 | testsavantState = 'loaded'; |
| 240 | } catch (err: any) { |
| 241 | testsavantState = 'failed'; |
| 242 | testsavantLoadError = err?.message ?? String(err); |
| 243 | console.error('[security-classifier] Failed to load TestSavantAI:', testsavantLoadError); |
| 244 | } |
| 245 | })(); |
| 246 | return loadPromise; |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Scan text content for prompt injection. Intended for page snapshots, tool |
no test coverage detected