npm package: @gristup/security-scanner
Description: Security scanner for Grist custom widgets - Detects potential security threats in widget code
Author: GristUp
Latest version: 1.0.0

# @gristup/security-scanner

🔒 **Security scanner for Grist custom widgets** - Detects potential security threats in widget code including XSS, code injection, data exfiltration, and more.

[![npm version](https://badge.fury.io/js/@gristup%2Fsecurity-scanner.svg)](https://www.npmjs.com/package/@gristup/security-scanner)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- 🔍 **40+ security rules** covering common vulnerabilities
- 📊 **Security score** from 0-100
- 🏷️ **Categorized threats** (XSS, injection, keylogger, etc.)
- 🌐 **External JS analysis** - fetches and scans referenced scripts
- ✅ **Sanitization detection** - recognizes safe patterns
- 🎯 **Grist-aware** - understands normal widget patterns

## Installation

```bash
npm install @gristup/security-scanner
```

## Usage

### Scan a widget URL

```typescript
import { scanWidgetUrl } from '@gristup/security-scanner';

const result = await scanWidgetUrl('https://example.com/widget/index.html');

console.log(result.score);    // 0-100 (100 = safe)
console.log(result.status);   // 'clean' | 'warning' | 'danger' | 'error'
console.log(result.verdict);  // Human-readable verdict
console.log(result.matches);  // Array of security issues found
```

### Scan raw code

```typescript
import { scanCode } from '@gristup/security-scanner';

const code = `
  document.innerHTML = userInput;
  eval(data);
`;

const result = scanCode(code);
console.log(result.matches);
// [
//   { ruleId: 'innerhtml-assign', severity: 'warning', ... },
//   { ruleId: 'eval', severity: 'critical', ... }
// ]
```

### Options

```typescript
const result = await scanWidgetUrl(url, {
  timeout: 10000,           // Request timeout in ms (default: 10000)
  userAgent: 'MyScanner',   // Custom user agent
  trustedDomains: [         // Domains to trust (won't penalize score)
    'cdn.example.com',
  ],
  skipExternalJs: false,    // Skip fetching external JS files
});
```

## Security Categories

The scanner checks for threats in these categories:

| Category | Description |
|----------|-------------|
| **Injection de code** | `eval()`, `new Function()`, `setTimeout(string)` |
| **XSS** | `innerHTML`, `document.write()` |
| **Vol de données** | `document.cookie`, clipboard access |
| **Obfuscation** | Hex strings, `fromCharCode`, `unescape` |
| **Keylogger** | Keyboard event listeners on document |
| **Exfiltration** | `sendBeacon`, tracking pixels |
| **Permissions** | Geolocation, camera, microphone |
| **Requêtes réseau** | External fetch, WebSocket |
| **Redirections** | `window.location`, `window.open` |
| **Scripts externes** | External `<script>` and `<iframe>` |
| **Crypto-mining** | CoinHive and similar |
| **Prototype pollution** | `__proto__` manipulation |

## Scan Result

```typescript
interface ScanResult {
  url: string;
  scannedAt: string;
  status: 'clean' | 'warning' | 'danger' | 'error';
  score: number;              // 0-100
  matches: ScanMatch[];       // Security issues found
  summary: string;            // Brief summary
  contentLength: number;      // Total code size scanned
  externalDomains: string[];  // External domains referenced
  categories: SecurityCategory[];
  verdict: string;            // Human-readable verdict
}

interface ScanMatch {
  ruleId: string;
  severity: 'critical' | 'warning' | 'info';
  label: string;
  description: string;
  line: number;
  snippet: string;
}
```

## Online Scanner

Try the scanner online at [gristup.fr/scan](https://www.gristup.fr/scan)

## Contributing

Contributions are welcome! Please open an issue or submit a pull request.

## License

MIT © [GristUp](https://www.gristup.fr)