@oculum/scanner 1.0.0
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.
- package/dist/formatters/cli-terminal.d.ts +27 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/formatters/cli-terminal.js +412 -0
- package/dist/formatters/cli-terminal.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +41 -0
- package/dist/formatters/github-comment.d.ts.map +1 -0
- package/dist/formatters/github-comment.js +306 -0
- package/dist/formatters/github-comment.js.map +1 -0
- package/dist/formatters/grouping.d.ts +52 -0
- package/dist/formatters/grouping.d.ts.map +1 -0
- package/dist/formatters/grouping.js +152 -0
- package/dist/formatters/grouping.js.map +1 -0
- package/dist/formatters/index.d.ts +9 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +35 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/formatters/vscode-diagnostic.js +151 -0
- package/dist/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/layer1/comments.d.ts +8 -0
- package/dist/layer1/comments.d.ts.map +1 -0
- package/dist/layer1/comments.js +203 -0
- package/dist/layer1/comments.js.map +1 -0
- package/dist/layer1/config-audit.d.ts +8 -0
- package/dist/layer1/config-audit.d.ts.map +1 -0
- package/dist/layer1/config-audit.js +252 -0
- package/dist/layer1/config-audit.js.map +1 -0
- package/dist/layer1/entropy.d.ts +8 -0
- package/dist/layer1/entropy.d.ts.map +1 -0
- package/dist/layer1/entropy.js +500 -0
- package/dist/layer1/entropy.js.map +1 -0
- package/dist/layer1/file-flags.d.ts +7 -0
- package/dist/layer1/file-flags.d.ts.map +1 -0
- package/dist/layer1/file-flags.js +112 -0
- package/dist/layer1/file-flags.js.map +1 -0
- package/dist/layer1/index.d.ts +36 -0
- package/dist/layer1/index.d.ts.map +1 -0
- package/dist/layer1/index.js +132 -0
- package/dist/layer1/index.js.map +1 -0
- package/dist/layer1/patterns.d.ts +8 -0
- package/dist/layer1/patterns.d.ts.map +1 -0
- package/dist/layer1/patterns.js +482 -0
- package/dist/layer1/patterns.js.map +1 -0
- package/dist/layer1/urls.d.ts +8 -0
- package/dist/layer1/urls.d.ts.map +1 -0
- package/dist/layer1/urls.js +296 -0
- package/dist/layer1/urls.js.map +1 -0
- package/dist/layer1/weak-crypto.d.ts +7 -0
- package/dist/layer1/weak-crypto.d.ts.map +1 -0
- package/dist/layer1/weak-crypto.js +291 -0
- package/dist/layer1/weak-crypto.js.map +1 -0
- package/dist/layer2/ai-agent-tools.d.ts +19 -0
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
- package/dist/layer2/ai-agent-tools.js +528 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -0
- package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
- package/dist/layer2/ai-endpoint-protection.js +332 -0
- package/dist/layer2/ai-endpoint-protection.js.map +1 -0
- package/dist/layer2/ai-execution-sinks.d.ts +18 -0
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
- package/dist/layer2/ai-execution-sinks.js +496 -0
- package/dist/layer2/ai-execution-sinks.js.map +1 -0
- package/dist/layer2/ai-fingerprinting.d.ts +7 -0
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
- package/dist/layer2/ai-fingerprinting.js +654 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.js +356 -0
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
- package/dist/layer2/ai-rag-safety.d.ts +21 -0
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
- package/dist/layer2/ai-rag-safety.js +459 -0
- package/dist/layer2/ai-rag-safety.js.map +1 -0
- package/dist/layer2/ai-schema-validation.d.ts +25 -0
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
- package/dist/layer2/ai-schema-validation.js +375 -0
- package/dist/layer2/ai-schema-validation.js.map +1 -0
- package/dist/layer2/auth-antipatterns.d.ts +20 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
- package/dist/layer2/auth-antipatterns.js +333 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -0
- package/dist/layer2/byok-patterns.d.ts +12 -0
- package/dist/layer2/byok-patterns.d.ts.map +1 -0
- package/dist/layer2/byok-patterns.js +299 -0
- package/dist/layer2/byok-patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions.d.ts +7 -0
- package/dist/layer2/dangerous-functions.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions.js +1375 -0
- package/dist/layer2/dangerous-functions.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +16 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -0
- package/dist/layer2/data-exposure.js +279 -0
- package/dist/layer2/data-exposure.js.map +1 -0
- package/dist/layer2/framework-checks.d.ts +7 -0
- package/dist/layer2/framework-checks.d.ts.map +1 -0
- package/dist/layer2/framework-checks.js +388 -0
- package/dist/layer2/framework-checks.js.map +1 -0
- package/dist/layer2/index.d.ts +58 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +380 -0
- package/dist/layer2/index.js.map +1 -0
- package/dist/layer2/logic-gates.d.ts +7 -0
- package/dist/layer2/logic-gates.d.ts.map +1 -0
- package/dist/layer2/logic-gates.js +182 -0
- package/dist/layer2/logic-gates.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +7 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -0
- package/dist/layer2/risky-imports.js +161 -0
- package/dist/layer2/risky-imports.js.map +1 -0
- package/dist/layer2/variables.d.ts +8 -0
- package/dist/layer2/variables.d.ts.map +1 -0
- package/dist/layer2/variables.js +152 -0
- package/dist/layer2/variables.js.map +1 -0
- package/dist/layer3/anthropic.d.ts +83 -0
- package/dist/layer3/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic.js +1745 -0
- package/dist/layer3/anthropic.js.map +1 -0
- package/dist/layer3/index.d.ts +24 -0
- package/dist/layer3/index.d.ts.map +1 -0
- package/dist/layer3/index.js +119 -0
- package/dist/layer3/index.js.map +1 -0
- package/dist/layer3/openai.d.ts +25 -0
- package/dist/layer3/openai.d.ts.map +1 -0
- package/dist/layer3/openai.js +238 -0
- package/dist/layer3/openai.js.map +1 -0
- package/dist/layer3/package-check.d.ts +63 -0
- package/dist/layer3/package-check.d.ts.map +1 -0
- package/dist/layer3/package-check.js +508 -0
- package/dist/layer3/package-check.js.map +1 -0
- package/dist/modes/incremental.d.ts +66 -0
- package/dist/modes/incremental.d.ts.map +1 -0
- package/dist/modes/incremental.js +200 -0
- package/dist/modes/incremental.js.map +1 -0
- package/dist/tiers.d.ts +125 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +234 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-helper-detector.d.ts +56 -0
- package/dist/utils/auth-helper-detector.d.ts.map +1 -0
- package/dist/utils/auth-helper-detector.js +360 -0
- package/dist/utils/auth-helper-detector.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +96 -0
- package/dist/utils/context-helpers.d.ts.map +1 -0
- package/dist/utils/context-helpers.js +493 -0
- package/dist/utils/context-helpers.js.map +1 -0
- package/dist/utils/diff-detector.d.ts +53 -0
- package/dist/utils/diff-detector.d.ts.map +1 -0
- package/dist/utils/diff-detector.js +104 -0
- package/dist/utils/diff-detector.js.map +1 -0
- package/dist/utils/diff-parser.d.ts +80 -0
- package/dist/utils/diff-parser.d.ts.map +1 -0
- package/dist/utils/diff-parser.js +202 -0
- package/dist/utils/diff-parser.js.map +1 -0
- package/dist/utils/imported-auth-detector.d.ts +37 -0
- package/dist/utils/imported-auth-detector.d.ts.map +1 -0
- package/dist/utils/imported-auth-detector.js +251 -0
- package/dist/utils/imported-auth-detector.js.map +1 -0
- package/dist/utils/middleware-detector.d.ts +55 -0
- package/dist/utils/middleware-detector.d.ts.map +1 -0
- package/dist/utils/middleware-detector.js +260 -0
- package/dist/utils/middleware-detector.js.map +1 -0
- package/dist/utils/oauth-flow-detector.d.ts +41 -0
- package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
- package/dist/utils/oauth-flow-detector.js +202 -0
- package/dist/utils/oauth-flow-detector.js.map +1 -0
- package/dist/utils/path-exclusions.d.ts +55 -0
- package/dist/utils/path-exclusions.d.ts.map +1 -0
- package/dist/utils/path-exclusions.js +222 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/project-context-builder.d.ts +119 -0
- package/dist/utils/project-context-builder.d.ts.map +1 -0
- package/dist/utils/project-context-builder.js +534 -0
- package/dist/utils/project-context-builder.js.map +1 -0
- package/dist/utils/registry-clients.d.ts +93 -0
- package/dist/utils/registry-clients.d.ts.map +1 -0
- package/dist/utils/registry-clients.js +273 -0
- package/dist/utils/registry-clients.js.map +1 -0
- package/dist/utils/trpc-analyzer.d.ts +78 -0
- package/dist/utils/trpc-analyzer.d.ts.map +1 -0
- package/dist/utils/trpc-analyzer.js +297 -0
- package/dist/utils/trpc-analyzer.js.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
- package/src/__tests__/benchmark/fixtures/index.ts +68 -0
- package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
- package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
- package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
- package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
- package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
- package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
- package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
- package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
- package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
- package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
- package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
- package/src/__tests__/benchmark/index.ts +29 -0
- package/src/__tests__/benchmark/run-benchmark.ts +144 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
- package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
- package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
- package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
- package/src/__tests__/benchmark/types.ts +144 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
- package/src/__tests__/regression/known-false-positives.test.ts +467 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
- package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
- package/src/__tests__/validation/analyze-results.ts +542 -0
- package/src/__tests__/validation/extract-for-triage.ts +146 -0
- package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
- package/src/__tests__/validation/run-validation.ts +364 -0
- package/src/__tests__/validation/triage-template.md +132 -0
- package/src/formatters/cli-terminal.ts +446 -0
- package/src/formatters/github-comment.ts +382 -0
- package/src/formatters/grouping.ts +190 -0
- package/src/formatters/index.ts +47 -0
- package/src/formatters/vscode-diagnostic.ts +243 -0
- package/src/index.ts +823 -0
- package/src/layer1/comments.ts +218 -0
- package/src/layer1/config-audit.ts +289 -0
- package/src/layer1/entropy.ts +583 -0
- package/src/layer1/file-flags.ts +127 -0
- package/src/layer1/index.ts +181 -0
- package/src/layer1/patterns.ts +516 -0
- package/src/layer1/urls.ts +334 -0
- package/src/layer1/weak-crypto.ts +328 -0
- package/src/layer2/ai-agent-tools.ts +601 -0
- package/src/layer2/ai-endpoint-protection.ts +387 -0
- package/src/layer2/ai-execution-sinks.ts +580 -0
- package/src/layer2/ai-fingerprinting.ts +758 -0
- package/src/layer2/ai-prompt-hygiene.ts +411 -0
- package/src/layer2/ai-rag-safety.ts +511 -0
- package/src/layer2/ai-schema-validation.ts +421 -0
- package/src/layer2/auth-antipatterns.ts +394 -0
- package/src/layer2/byok-patterns.ts +336 -0
- package/src/layer2/dangerous-functions.ts +1563 -0
- package/src/layer2/data-exposure.ts +315 -0
- package/src/layer2/framework-checks.ts +433 -0
- package/src/layer2/index.ts +473 -0
- package/src/layer2/logic-gates.ts +206 -0
- package/src/layer2/risky-imports.ts +186 -0
- package/src/layer2/variables.ts +166 -0
- package/src/layer3/anthropic.ts +2030 -0
- package/src/layer3/index.ts +130 -0
- package/src/layer3/package-check.ts +604 -0
- package/src/modes/incremental.ts +293 -0
- package/src/tiers.ts +318 -0
- package/src/types.ts +284 -0
- package/src/utils/auth-helper-detector.ts +443 -0
- package/src/utils/context-helpers.ts +535 -0
- package/src/utils/diff-detector.ts +135 -0
- package/src/utils/diff-parser.ts +272 -0
- package/src/utils/imported-auth-detector.ts +320 -0
- package/src/utils/middleware-detector.ts +333 -0
- package/src/utils/oauth-flow-detector.ts +246 -0
- package/src/utils/path-exclusions.ts +266 -0
- package/src/utils/project-context-builder.ts +707 -0
- package/src/utils/registry-clients.ts +351 -0
- package/src/utils/trpc-analyzer.ts +382 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded Secrets Test Fixtures
|
|
3
|
+
* Tests for detecting API keys, tokens, passwords, and other secrets in code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const hardcodedSecretsTests: TestGroup = {
|
|
9
|
+
name: 'Hardcoded Secrets',
|
|
10
|
+
tier: 'A',
|
|
11
|
+
layer: 1,
|
|
12
|
+
description: 'Detection of API keys, tokens, passwords, and other secrets hardcoded in source code',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'Hardcoded Secrets - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['hardcoded_secret'],
|
|
19
|
+
description: 'Various hardcoded secrets that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/config/secrets.ts',
|
|
22
|
+
content: `
|
|
23
|
+
// AWS Credentials (Critical)
|
|
24
|
+
const AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
|
|
25
|
+
const AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
26
|
+
|
|
27
|
+
// API Keys (Critical)
|
|
28
|
+
const OPENAI_API_KEY = "sk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx"
|
|
29
|
+
const ANTHROPIC_API_KEY = "sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789"
|
|
30
|
+
const STRIPE_SECRET_KEY = "sk_live_51AbCdEfGhIjKlMnOpQrStUvWxYz0123456789"
|
|
31
|
+
const STRIPE_PUBLISHABLE_KEY = "pk_live_51AbCdEfGhIjKlMnOpQrStUvWxYz0123456789"
|
|
32
|
+
|
|
33
|
+
// GitHub Tokens (High)
|
|
34
|
+
const GITHUB_TOKEN = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
35
|
+
const GITHUB_PAT = "github_pat_11ABCDEFG_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
36
|
+
|
|
37
|
+
// Database Credentials (Critical)
|
|
38
|
+
const DATABASE_URL = "postgresql://admin:SuperSecret123!@prod-db.example.com:5432/production"
|
|
39
|
+
const MONGO_URI = "mongodb+srv://admin:password123@cluster0.mongodb.net/prod?retryWrites=true"
|
|
40
|
+
const REDIS_URL = "redis://:secretpassword@redis.example.com:6379"
|
|
41
|
+
|
|
42
|
+
// JWT Secrets (High)
|
|
43
|
+
const JWT_SECRET = "super_secret_jwt_signing_key_12345"
|
|
44
|
+
const SESSION_SECRET = "my-session-secret-dont-share"
|
|
45
|
+
|
|
46
|
+
// Private Keys (Critical)
|
|
47
|
+
const PRIVATE_KEY = \`-----BEGIN RSA PRIVATE KEY-----
|
|
48
|
+
MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy...
|
|
49
|
+
-----END RSA PRIVATE KEY-----\`
|
|
50
|
+
|
|
51
|
+
// Slack/Discord Webhooks (Medium)
|
|
52
|
+
const SLACK_WEBHOOK = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
|
|
53
|
+
const DISCORD_WEBHOOK = "https://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOP"
|
|
54
|
+
|
|
55
|
+
// SendGrid/Twilio (High)
|
|
56
|
+
const SENDGRID_API_KEY = "SG.xxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
57
|
+
const TWILIO_AUTH_TOKEN = "12345678901234567890123456789012"
|
|
58
|
+
`,
|
|
59
|
+
language: 'typescript',
|
|
60
|
+
size: 2000,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Hardcoded Secrets - Cloud Provider Keys',
|
|
65
|
+
expectFindings: true,
|
|
66
|
+
expectedCategories: ['hardcoded_secret'],
|
|
67
|
+
description: 'Cloud provider API keys and service account credentials',
|
|
68
|
+
file: {
|
|
69
|
+
path: 'src/config/cloud-keys.ts',
|
|
70
|
+
content: `
|
|
71
|
+
// Google Cloud
|
|
72
|
+
const GCP_API_KEY = "AIzaSyDaGmWKa4JsXZ-HjGw7ISLn_3namBGewQe"
|
|
73
|
+
const GCP_SERVICE_ACCOUNT = {
|
|
74
|
+
"type": "service_account",
|
|
75
|
+
"project_id": "my-project",
|
|
76
|
+
"private_key_id": "key123",
|
|
77
|
+
"private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBg..."
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Azure
|
|
81
|
+
const AZURE_STORAGE_KEY = "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=abc123def456..."
|
|
82
|
+
const AZURE_CLIENT_SECRET = "~Ab1.cDeF2gHiJ3kLmN4oPqR5sTuV6wXyZ"
|
|
83
|
+
|
|
84
|
+
// DigitalOcean
|
|
85
|
+
const DO_API_TOKEN = "dop_v1_abc123def456ghi789jkl012mno345pqr678stu901"
|
|
86
|
+
|
|
87
|
+
// Cloudflare
|
|
88
|
+
const CF_API_KEY = "c2547eb745079dac9320b638f5e225cf483cc5cfdda41"
|
|
89
|
+
`,
|
|
90
|
+
language: 'typescript',
|
|
91
|
+
size: 800,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
|
|
96
|
+
falseNegatives: [
|
|
97
|
+
{
|
|
98
|
+
name: 'Hardcoded Secrets - False Negatives',
|
|
99
|
+
expectFindings: false,
|
|
100
|
+
description: 'Patterns that should NOT be flagged as hardcoded secrets',
|
|
101
|
+
file: {
|
|
102
|
+
path: 'src/config/config.ts',
|
|
103
|
+
content: `
|
|
104
|
+
// Environment variables - SAFE
|
|
105
|
+
const API_KEY = process.env.API_KEY
|
|
106
|
+
const DATABASE_URL = process.env.DATABASE_URL
|
|
107
|
+
const JWT_SECRET = process.env.JWT_SECRET || ''
|
|
108
|
+
|
|
109
|
+
// Next.js public env vars - SAFE
|
|
110
|
+
const NEXT_PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL
|
|
111
|
+
|
|
112
|
+
// Placeholder values - SAFE
|
|
113
|
+
const PLACEHOLDER_KEY = "your-api-key-here"
|
|
114
|
+
const EXAMPLE_SECRET = "<INSERT_SECRET>"
|
|
115
|
+
const TODO_KEY = "TODO: replace with real key"
|
|
116
|
+
const TEST_KEY = "test_key_not_real"
|
|
117
|
+
|
|
118
|
+
// Type definitions - SAFE
|
|
119
|
+
interface Config {
|
|
120
|
+
apiKey: string
|
|
121
|
+
secretKey: string
|
|
122
|
+
jwtSecret: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Function parameters - SAFE
|
|
126
|
+
function authenticate(apiKey: string, secretKey: string) {
|
|
127
|
+
return fetch('/api/auth', {
|
|
128
|
+
headers: { 'Authorization': \`Bearer \${apiKey}\` }
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Comments discussing secrets - SAFE (not actual secrets)
|
|
133
|
+
// The API key should be stored in AWS_SECRET_ACCESS_KEY
|
|
134
|
+
// Format: sk-ant-api03-xxxxx
|
|
135
|
+
|
|
136
|
+
// Documentation examples - SAFE
|
|
137
|
+
/*
|
|
138
|
+
* Example usage:
|
|
139
|
+
* const client = new Client({ apiKey: "sk-..." })
|
|
140
|
+
*/
|
|
141
|
+
`,
|
|
142
|
+
language: 'typescript',
|
|
143
|
+
size: 1000,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'Hardcoded Secrets - Env File Template',
|
|
148
|
+
expectFindings: false,
|
|
149
|
+
description: '.env.example files should not be flagged',
|
|
150
|
+
file: {
|
|
151
|
+
path: '.env.example',
|
|
152
|
+
content: `
|
|
153
|
+
# Database
|
|
154
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
|
|
155
|
+
|
|
156
|
+
# API Keys (replace with your own)
|
|
157
|
+
OPENAI_API_KEY=sk-your-key-here
|
|
158
|
+
ANTHROPIC_API_KEY=sk-ant-your-key-here
|
|
159
|
+
|
|
160
|
+
# Auth
|
|
161
|
+
JWT_SECRET=generate-a-random-string-here
|
|
162
|
+
NEXTAUTH_SECRET=another-random-string
|
|
163
|
+
|
|
164
|
+
# Third party
|
|
165
|
+
STRIPE_SECRET_KEY=sk_test_your_key
|
|
166
|
+
STRIPE_PUBLISHABLE_KEY=pk_test_your_key
|
|
167
|
+
`,
|
|
168
|
+
language: 'text',
|
|
169
|
+
size: 400,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Entropy String Detection Test Fixtures
|
|
3
|
+
* Tests for detecting potential secrets based on Shannon entropy
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const highEntropyTests: TestGroup = {
|
|
9
|
+
name: 'High-Entropy Strings',
|
|
10
|
+
tier: 'B',
|
|
11
|
+
layer: 1,
|
|
12
|
+
description: 'Detection of high-entropy strings that may be secrets',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'High-Entropy - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['high_entropy_string'],
|
|
19
|
+
description: 'High-entropy strings that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/config/secrets.ts',
|
|
22
|
+
content: `
|
|
23
|
+
// Random strings that look like secrets - should be flagged
|
|
24
|
+
const randomSecret = 'aB3xK9mPqR5tL7nW2cF4hJ6dG8vS0yU1'
|
|
25
|
+
const anotherSecret = 'xYz123AbC456dEf789GhI012JkL345MnO'
|
|
26
|
+
const hexSecret = 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8'
|
|
27
|
+
|
|
28
|
+
// Base64-encoded looking strings
|
|
29
|
+
const base64Secret = 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo='
|
|
30
|
+
const longBase64 = 'SGVsbG9Xb3JsZFRoaXNJc0FMb25nU3RyaW5nRm9yVGVzdGluZw=='
|
|
31
|
+
|
|
32
|
+
// Mixed alphanumeric high-entropy
|
|
33
|
+
const mixedSecret = 'Qw3rTy7U1oP9aS2dF4gH6jK8lZ0xCvB'
|
|
34
|
+
const complexSecret = 'n5X2mK8pL3qW7rT9yU4iO1aS6dF0gH'
|
|
35
|
+
|
|
36
|
+
// Strings that could be API keys
|
|
37
|
+
const potentialKey = 'sk_live_51JvXxxxxxxxxxxxxxxxxxxx12345'
|
|
38
|
+
const anotherKey = 'rk_test_aBcDeFgHiJkLmNoPqRsTuVwXyZ'
|
|
39
|
+
|
|
40
|
+
// JWT-like high entropy segments
|
|
41
|
+
const jwtPayload = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0'
|
|
42
|
+
`,
|
|
43
|
+
language: 'typescript',
|
|
44
|
+
size: 800,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
falseNegatives: [
|
|
50
|
+
{
|
|
51
|
+
name: 'High-Entropy - False Negatives (Safe Patterns)',
|
|
52
|
+
expectFindings: false,
|
|
53
|
+
description: 'High-entropy but safe patterns that should NOT be flagged',
|
|
54
|
+
allowedInfoFindings: [
|
|
55
|
+
{
|
|
56
|
+
category: 'high_entropy_string',
|
|
57
|
+
maxCount: 5,
|
|
58
|
+
reason: 'Some edge cases may still generate info findings requiring AI validation',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
category: 'hardcoded_secret',
|
|
62
|
+
maxCount: 3,
|
|
63
|
+
reason: 'Hashes and webhook URLs may trigger secret detection',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
category: 'sensitive_url',
|
|
67
|
+
maxCount: 3,
|
|
68
|
+
reason: 'URLs in test data may trigger URL detection',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
category: 'sensitive_variable',
|
|
72
|
+
maxCount: 2,
|
|
73
|
+
reason: 'Variable names may trigger sensitive variable detection',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
file: {
|
|
77
|
+
path: 'src/components/Dashboard.tsx',
|
|
78
|
+
content: `
|
|
79
|
+
import { useState, useEffect } from 'react'
|
|
80
|
+
|
|
81
|
+
// CSS/Tailwind classes - SAFE (looks high entropy but is CSS)
|
|
82
|
+
const styles = 'flex items-center justify-between p-4 bg-gray-100 rounded-lg shadow-md'
|
|
83
|
+
const moreStyles = 'text-sm font-medium text-gray-700 hover:text-blue-600 transition-colors duration-200'
|
|
84
|
+
const complexTailwind = 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 px-6 py-8 bg-gradient-to-r'
|
|
85
|
+
|
|
86
|
+
// UUIDs - SAFE (standard format)
|
|
87
|
+
const userId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
|
88
|
+
|
|
89
|
+
// Short references - SAFE
|
|
90
|
+
const commitRef = 'a1b2c3d'
|
|
91
|
+
|
|
92
|
+
// Natural language content - SAFE
|
|
93
|
+
const description = 'This is a longer description that explains what the feature does and how to use it properly'
|
|
94
|
+
const message = 'Please enter your email address to continue with the registration process'
|
|
95
|
+
|
|
96
|
+
// UI strings with model names - SAFE
|
|
97
|
+
const modelLabel = 'Claude 3.5 Sonnet - Best for chat and embeddings with high accuracy'
|
|
98
|
+
const tooltip = 'Uses GPT-4 Turbo for advanced reasoning and complex analysis tasks'
|
|
99
|
+
|
|
100
|
+
// URLs - SAFE
|
|
101
|
+
const docsUrl = 'https://docs.myapp.com/getting-started'
|
|
102
|
+
|
|
103
|
+
// File paths - SAFE
|
|
104
|
+
const configPath = '/home/user/.config/myapp/settings/production.json'
|
|
105
|
+
const assetPath = '/assets/images/icons/dashboard/analytics/chart-line.svg'
|
|
106
|
+
|
|
107
|
+
// CSS gradients and colors - SAFE
|
|
108
|
+
const gradient = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
|
109
|
+
const complexColor = 'rgba(102, 126, 234, 0.85)'
|
|
110
|
+
|
|
111
|
+
// Template literals with code expressions - SAFE
|
|
112
|
+
const formattedTime = \`Synced \${lastSync.toLocaleString()} - \${hours}h ago\`
|
|
113
|
+
const displayPrice = \`$\${price.toFixed(2).padStart(8, '0')}\`
|
|
114
|
+
|
|
115
|
+
// Dates/timestamps - SAFE
|
|
116
|
+
const timestamp = '2024-01-15T14:30:00.000Z'
|
|
117
|
+
const formattedDate = '2024-01-15 14:30:00+00:00'
|
|
118
|
+
|
|
119
|
+
// Email addresses - SAFE
|
|
120
|
+
const supportEmail = 'support+test-user@example-company.com'
|
|
121
|
+
|
|
122
|
+
// Regex patterns (route matchers) - SAFE
|
|
123
|
+
const routePattern = '/api/v1/users/[id]/posts/(?:comments|reactions)'
|
|
124
|
+
const pathMatcher = '^\\/api\\/v[0-9]+\\/(?:users|posts)\\/(.*)'
|
|
125
|
+
`,
|
|
126
|
+
language: 'typescript',
|
|
127
|
+
size: 2200,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'High-Entropy - False Negatives (CSS-in-JS)',
|
|
132
|
+
expectFindings: false,
|
|
133
|
+
description: 'CSS-in-JS patterns that should NOT be flagged',
|
|
134
|
+
allowedInfoFindings: [
|
|
135
|
+
{
|
|
136
|
+
category: 'high_entropy_string',
|
|
137
|
+
maxCount: 1,
|
|
138
|
+
reason: 'Complex CSS may occasionally trigger',
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
file: {
|
|
142
|
+
path: 'src/styles/theme.ts',
|
|
143
|
+
content: `
|
|
144
|
+
import styled from 'styled-components'
|
|
145
|
+
|
|
146
|
+
// Styled components - SAFE
|
|
147
|
+
const Button = styled.button\`
|
|
148
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
149
|
+
color: #ffffff;
|
|
150
|
+
padding: 12px 24px;
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
|
154
|
+
|
|
155
|
+
&:hover {
|
|
156
|
+
transform: translateY(-2px);
|
|
157
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
158
|
+
}
|
|
159
|
+
\`
|
|
160
|
+
|
|
161
|
+
// Emotion CSS - SAFE
|
|
162
|
+
const containerStyles = css\`
|
|
163
|
+
display: grid;
|
|
164
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
165
|
+
gap: 24px;
|
|
166
|
+
padding: 32px;
|
|
167
|
+
background: #f8fafc;
|
|
168
|
+
border-radius: 12px;
|
|
169
|
+
\`
|
|
170
|
+
|
|
171
|
+
// Inline styles object - SAFE
|
|
172
|
+
const inlineStyle = {
|
|
173
|
+
background: 'linear-gradient(to right, #4facfe 0%, #00f2fe 100%)',
|
|
174
|
+
boxShadow: '0 10px 40px rgba(0, 0, 0, 0.15)',
|
|
175
|
+
transform: 'perspective(1000px) rotateY(5deg)',
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// CSS custom properties - SAFE
|
|
179
|
+
const cssVars = {
|
|
180
|
+
'--primary-gradient': 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
181
|
+
'--shadow-color': 'rgba(102, 126, 234, 0.25)',
|
|
182
|
+
}
|
|
183
|
+
`,
|
|
184
|
+
language: 'typescript',
|
|
185
|
+
size: 1000,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'Test File High-Entropy',
|
|
190
|
+
expectFindings: false,
|
|
191
|
+
description: 'Test files with high-entropy test data',
|
|
192
|
+
allowedInfoFindings: [
|
|
193
|
+
{
|
|
194
|
+
category: 'high_entropy_string',
|
|
195
|
+
maxCount: 3,
|
|
196
|
+
reason: 'Test data may have some high-entropy patterns',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
category: 'hardcoded_secret',
|
|
200
|
+
maxCount: 2,
|
|
201
|
+
reason: 'Test secrets may be detected at low severity',
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
file: {
|
|
205
|
+
path: 'src/__tests__/crypto.test.ts',
|
|
206
|
+
content: `
|
|
207
|
+
import { describe, it, expect } from 'vitest'
|
|
208
|
+
import { hash, encrypt, decrypt } from '../crypto'
|
|
209
|
+
|
|
210
|
+
describe('Crypto utils', () => {
|
|
211
|
+
// Test vectors with known outputs - OK in tests
|
|
212
|
+
const testKey = 'aB3xK9mPqR5tL7nW2cF4hJ6dG8vS0yU1'
|
|
213
|
+
const testIV = '0123456789abcdef0123456789abcdef'
|
|
214
|
+
const expectedHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
|
215
|
+
|
|
216
|
+
it('should hash correctly', () => {
|
|
217
|
+
const result = hash('test-input')
|
|
218
|
+
expect(result).toMatch(/^[a-f0-9]{64}$/)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should encrypt and decrypt', () => {
|
|
222
|
+
const plaintext = 'Hello, World!'
|
|
223
|
+
const encrypted = encrypt(plaintext, testKey)
|
|
224
|
+
const decrypted = decrypt(encrypted, testKey)
|
|
225
|
+
expect(decrypted).toBe(plaintext)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
`,
|
|
229
|
+
language: 'typescript',
|
|
230
|
+
size: 700,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 Test Fixtures Index
|
|
3
|
+
* Exports all Layer 1 (Surface Scan) test fixtures
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { hardcodedSecretsTests } from './hardcoded-secrets'
|
|
7
|
+
export { weakCryptoTests } from './weak-crypto'
|
|
8
|
+
export { sensitiveUrlsTests } from './sensitive-urls'
|
|
9
|
+
// M6: New Layer 1 fixtures
|
|
10
|
+
export { highEntropyTests } from './high-entropy'
|
|
11
|
+
export { configAuditTests } from './config-audit'
|
|
12
|
+
|
|
13
|
+
import type { TestGroup } from '../../types'
|
|
14
|
+
import { hardcodedSecretsTests } from './hardcoded-secrets'
|
|
15
|
+
import { weakCryptoTests } from './weak-crypto'
|
|
16
|
+
import { sensitiveUrlsTests } from './sensitive-urls'
|
|
17
|
+
// M6: New Layer 1 fixtures
|
|
18
|
+
import { highEntropyTests } from './high-entropy'
|
|
19
|
+
import { configAuditTests } from './config-audit'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* All Layer 1 test groups
|
|
23
|
+
*/
|
|
24
|
+
export const layer1TestGroups: TestGroup[] = [
|
|
25
|
+
hardcodedSecretsTests,
|
|
26
|
+
weakCryptoTests,
|
|
27
|
+
sensitiveUrlsTests,
|
|
28
|
+
// M6: New Layer 1 fixtures
|
|
29
|
+
highEntropyTests,
|
|
30
|
+
configAuditTests,
|
|
31
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sensitive URLs Test Fixtures
|
|
3
|
+
* Tests for detecting sensitive URLs with embedded credentials or internal endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const sensitiveUrlsTests: TestGroup = {
|
|
9
|
+
name: 'Sensitive URLs',
|
|
10
|
+
tier: 'A',
|
|
11
|
+
layer: 1,
|
|
12
|
+
description: 'Detection of URLs with embedded credentials, internal endpoints, and webhook URLs',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'Sensitive URLs - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['sensitive_url', 'hardcoded_secret'],
|
|
19
|
+
description: 'Sensitive URLs that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/config/endpoints.ts',
|
|
22
|
+
content: `
|
|
23
|
+
// Webhook URLs with embedded tokens (High)
|
|
24
|
+
const SLACK_WEBHOOK = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX"
|
|
25
|
+
const DISCORD_WEBHOOK = "https://discord.com/api/webhooks/123456789/abcdef"
|
|
26
|
+
const GITHUB_WEBHOOK = "https://api.github.com/repos/owner/repo/hooks?token=abc123"
|
|
27
|
+
|
|
28
|
+
// Internal infrastructure URLs (Medium)
|
|
29
|
+
const INTERNAL_API = "http://internal-api.prod.company.com/admin"
|
|
30
|
+
const KUBERNETES_API = "https://10.0.0.1:6443/api/v1/namespaces"
|
|
31
|
+
const VAULT_URL = "http://vault.internal:8200/v1/secret/data"
|
|
32
|
+
|
|
33
|
+
// URLs with embedded credentials (Critical)
|
|
34
|
+
const DB_ADMIN = "https://admin:password@db.example.com:5432"
|
|
35
|
+
const REDIS_ADMIN = "redis://:secretpass@redis.prod.internal:6379"
|
|
36
|
+
const GRAFANA_URL = "https://admin:grafana123@monitoring.internal/d/dashboard"
|
|
37
|
+
|
|
38
|
+
// Production hardcoded endpoints (Medium)
|
|
39
|
+
const PROD_API = "https://api.production.company.com/v1"
|
|
40
|
+
const ADMIN_PANEL = "https://admin.production.company.com/dashboard"
|
|
41
|
+
|
|
42
|
+
// S3 with access key in URL (High)
|
|
43
|
+
const S3_URL = "https://AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI@s3.amazonaws.com/bucket"
|
|
44
|
+
`,
|
|
45
|
+
language: 'typescript',
|
|
46
|
+
size: 1000,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
|
|
51
|
+
falseNegatives: [
|
|
52
|
+
{
|
|
53
|
+
name: 'Sensitive URLs - False Negatives',
|
|
54
|
+
expectFindings: false,
|
|
55
|
+
description: 'Safe URL patterns that should NOT be flagged',
|
|
56
|
+
allowedInfoFindings: [
|
|
57
|
+
{
|
|
58
|
+
category: 'sensitive_url',
|
|
59
|
+
maxCount: 2,
|
|
60
|
+
reason: 'Localhost URLs in dev context are info-level findings, not security issues',
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
file: {
|
|
64
|
+
path: 'src/config/urls.ts',
|
|
65
|
+
content: `
|
|
66
|
+
// Public API endpoints - SAFE
|
|
67
|
+
const PUBLIC_API = "https://api.example.com/v1"
|
|
68
|
+
const CDN_URL = "https://cdn.example.com/assets"
|
|
69
|
+
|
|
70
|
+
// Environment-based URLs - SAFE
|
|
71
|
+
const API_URL = process.env.API_URL
|
|
72
|
+
const WEBHOOK_URL = process.env.WEBHOOK_URL
|
|
73
|
+
|
|
74
|
+
// Localhost for development - SAFE (info at most)
|
|
75
|
+
const DEV_API = "http://localhost:3000/api"
|
|
76
|
+
const DEV_DB = "postgresql://localhost:5432/dev"
|
|
77
|
+
|
|
78
|
+
// Documentation URLs - SAFE
|
|
79
|
+
const DOCS_URL = "https://docs.example.com"
|
|
80
|
+
const SWAGGER_URL = "https://api.example.com/swagger"
|
|
81
|
+
|
|
82
|
+
// Template URLs - SAFE
|
|
83
|
+
const URL_TEMPLATE = "https://{subdomain}.example.com/{path}"
|
|
84
|
+
`,
|
|
85
|
+
language: 'typescript',
|
|
86
|
+
size: 500,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
}
|