shield360 1.1.0 → 1.2.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/README.md +330 -3
- package/dist/__tests__/build-info.test.js +8 -1
- package/dist/__tests__/build-info.test.js.map +1 -1
- package/dist/__tests__/code-location.test.js +1 -1
- package/dist/__tests__/masking.test.d.ts +1 -0
- package/dist/__tests__/masking.test.js +66 -0
- package/dist/__tests__/masking.test.js.map +1 -0
- package/dist/__tests__/register.e2e.test.d.ts +1 -0
- package/dist/__tests__/register.e2e.test.js +264 -0
- package/dist/__tests__/register.e2e.test.js.map +1 -0
- package/dist/__tests__/register.test.js +60 -0
- package/dist/__tests__/register.test.js.map +1 -1
- package/dist/agents/__tests__/agents.test.d.ts +1 -0
- package/dist/agents/__tests__/agents.test.js +529 -0
- package/dist/agents/__tests__/agents.test.js.map +1 -0
- package/dist/agents/index.d.ts +15 -0
- package/dist/agents/index.js +25 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/passport.d.ts +92 -0
- package/dist/agents/passport.js +301 -0
- package/dist/agents/passport.js.map +1 -0
- package/dist/agents/policy.d.ts +24 -0
- package/dist/agents/policy.js +93 -0
- package/dist/agents/policy.js.map +1 -0
- package/dist/agents/replay.d.ts +20 -0
- package/dist/agents/replay.js +47 -0
- package/dist/agents/replay.js.map +1 -0
- package/dist/agents/report.d.ts +30 -0
- package/dist/agents/report.js +93 -0
- package/dist/agents/report.js.map +1 -0
- package/dist/agents/types.d.ts +148 -0
- package/dist/agents/types.js +37 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/agents/verifier.d.ts +33 -0
- package/dist/agents/verifier.js +276 -0
- package/dist/agents/verifier.js.map +1 -0
- package/dist/ai-security/__tests__/bench.test.d.ts +1 -0
- package/dist/ai-security/__tests__/bench.test.js +50 -0
- package/dist/ai-security/__tests__/bench.test.js.map +1 -0
- package/dist/ai-security/__tests__/checks.test.d.ts +1 -0
- package/dist/ai-security/__tests__/checks.test.js +248 -0
- package/dist/ai-security/__tests__/checks.test.js.map +1 -0
- package/dist/ai-security/__tests__/config.test.d.ts +1 -0
- package/dist/ai-security/__tests__/config.test.js +164 -0
- package/dist/ai-security/__tests__/config.test.js.map +1 -0
- package/dist/ai-security/__tests__/detection.test.d.ts +1 -0
- package/dist/ai-security/__tests__/detection.test.js +114 -0
- package/dist/ai-security/__tests__/detection.test.js.map +1 -0
- package/dist/ai-security/__tests__/fixtures.d.ts +8 -0
- package/dist/ai-security/__tests__/fixtures.js +30 -0
- package/dist/ai-security/__tests__/fixtures.js.map +1 -0
- package/dist/ai-security/__tests__/hooks.test.d.ts +1 -0
- package/dist/ai-security/__tests__/hooks.test.js +174 -0
- package/dist/ai-security/__tests__/hooks.test.js.map +1 -0
- package/dist/ai-security/__tests__/stream.test.d.ts +1 -0
- package/dist/ai-security/__tests__/stream.test.js +85 -0
- package/dist/ai-security/__tests__/stream.test.js.map +1 -0
- package/dist/ai-security/config.d.ts +94 -0
- package/dist/ai-security/config.js +241 -0
- package/dist/ai-security/config.js.map +1 -0
- package/dist/ai-security/context.d.ts +45 -0
- package/dist/ai-security/context.js +108 -0
- package/dist/ai-security/context.js.map +1 -0
- package/dist/ai-security/custom-kinds.d.ts +23 -0
- package/dist/ai-security/custom-kinds.js +97 -0
- package/dist/ai-security/custom-kinds.js.map +1 -0
- package/dist/ai-security/events.d.ts +77 -0
- package/dist/ai-security/events.js +153 -0
- package/dist/ai-security/events.js.map +1 -0
- package/dist/ai-security/hooks.d.ts +48 -0
- package/dist/ai-security/hooks.js +461 -0
- package/dist/ai-security/hooks.js.map +1 -0
- package/dist/ai-security/index.d.ts +209 -0
- package/dist/ai-security/index.js +587 -0
- package/dist/ai-security/index.js.map +1 -0
- package/dist/ai-security/injection-rules.json +1024 -0
- package/dist/ai-security/injection.d.ts +46 -0
- package/dist/ai-security/injection.js +635 -0
- package/dist/ai-security/injection.js.map +1 -0
- package/dist/ai-security/jailbreak.d.ts +38 -0
- package/dist/ai-security/jailbreak.js +73 -0
- package/dist/ai-security/jailbreak.js.map +1 -0
- package/dist/ai-security/limits.d.ts +84 -0
- package/dist/ai-security/limits.js +195 -0
- package/dist/ai-security/limits.js.map +1 -0
- package/dist/ai-security/mcp.d.ts +82 -0
- package/dist/ai-security/mcp.js +150 -0
- package/dist/ai-security/mcp.js.map +1 -0
- package/dist/ai-security/output.d.ts +107 -0
- package/dist/ai-security/output.js +390 -0
- package/dist/ai-security/output.js.map +1 -0
- package/dist/ai-security/policy.d.ts +125 -0
- package/dist/ai-security/policy.js +196 -0
- package/dist/ai-security/policy.js.map +1 -0
- package/dist/ai-security/rag.d.ts +70 -0
- package/dist/ai-security/rag.js +174 -0
- package/dist/ai-security/rag.js.map +1 -0
- package/dist/ai-security/tool-rules.json +203 -0
- package/dist/ai-security/tools.d.ts +46 -0
- package/dist/ai-security/tools.js +62 -0
- package/dist/ai-security/tools.js.map +1 -0
- package/dist/budgets/__tests__/budgets.test.d.ts +1 -0
- package/dist/budgets/__tests__/budgets.test.js +134 -0
- package/dist/budgets/__tests__/budgets.test.js.map +1 -0
- package/dist/budgets/auto.d.ts +25 -0
- package/dist/budgets/auto.js +63 -0
- package/dist/budgets/auto.js.map +1 -0
- package/dist/budgets/index.d.ts +110 -0
- package/dist/budgets/index.js +239 -0
- package/dist/budgets/index.js.map +1 -0
- package/dist/customer/__tests__/customer.test.d.ts +1 -0
- package/dist/customer/__tests__/customer.test.js +155 -0
- package/dist/customer/__tests__/customer.test.js.map +1 -0
- package/dist/customer/index.d.ts +48 -0
- package/dist/customer/index.js +132 -0
- package/dist/customer/index.js.map +1 -0
- package/dist/feedback/__tests__/feedback.test.d.ts +1 -0
- package/dist/feedback/__tests__/feedback.test.js +80 -0
- package/dist/feedback/__tests__/feedback.test.js.map +1 -0
- package/dist/feedback/index.d.ts +48 -0
- package/dist/feedback/index.js +97 -0
- package/dist/feedback/index.js.map +1 -0
- package/dist/flags/__tests__/flags.test.d.ts +1 -0
- package/dist/flags/__tests__/flags.test.js +301 -0
- package/dist/flags/__tests__/flags.test.js.map +1 -0
- package/dist/flags/client.d.ts +131 -0
- package/dist/flags/client.js +301 -0
- package/dist/flags/client.js.map +1 -0
- package/dist/flags/engine.d.ts +200 -0
- package/dist/flags/engine.js +677 -0
- package/dist/flags/engine.js.map +1 -0
- package/dist/flags/index.d.ts +4 -0
- package/dist/flags/index.js +16 -0
- package/dist/flags/index.js.map +1 -0
- package/dist/guard/__tests__/findings.test.js +1 -1
- package/dist/guard/__tests__/report.test.js +67 -0
- package/dist/guard/__tests__/report.test.js.map +1 -1
- package/dist/guard/heartbeat.d.ts +34 -0
- package/dist/guard/heartbeat.js +83 -0
- package/dist/guard/heartbeat.js.map +1 -0
- package/dist/guard/integration.d.ts +9 -0
- package/dist/guard/integration.js +4 -2
- package/dist/guard/integration.js.map +1 -1
- package/dist/guard/report.d.ts +24 -1
- package/dist/guard/report.js +67 -18
- package/dist/guard/report.js.map +1 -1
- package/dist/index.d.ts +88 -0
- package/dist/index.js +193 -2
- package/dist/index.js.map +1 -1
- package/dist/instrumentation/__tests__/langchain-wrapper.test.js +1 -1
- package/dist/instrumentation/__tests__/langchain-wrapper.test.js.map +1 -1
- package/dist/instrumentation/anthropic/wrapper.js +4 -0
- package/dist/instrumentation/anthropic/wrapper.js.map +1 -1
- package/dist/instrumentation/openai/wrapper.js +6 -0
- package/dist/instrumentation/openai/wrapper.js.map +1 -1
- package/dist/masking.d.ts +42 -0
- package/dist/masking.js +197 -0
- package/dist/masking.js.map +1 -0
- package/dist/otel/__tests__/app-logs-safety.test.d.ts +1 -0
- package/dist/otel/__tests__/app-logs-safety.test.js +129 -0
- package/dist/otel/__tests__/app-logs-safety.test.js.map +1 -0
- package/dist/otel/__tests__/app-logs.test.d.ts +1 -0
- package/dist/otel/__tests__/app-logs.test.js +269 -0
- package/dist/otel/__tests__/app-logs.test.js.map +1 -0
- package/dist/otel/__tests__/log-auto.test.d.ts +1 -0
- package/dist/otel/__tests__/log-auto.test.js +125 -0
- package/dist/otel/__tests__/log-auto.test.js.map +1 -0
- package/dist/otel/__tests__/log-bridges.test.d.ts +1 -0
- package/dist/otel/__tests__/log-bridges.test.js +170 -0
- package/dist/otel/__tests__/log-bridges.test.js.map +1 -0
- package/dist/otel/__tests__/system-metrics.test.d.ts +1 -0
- package/dist/otel/__tests__/system-metrics.test.js +79 -0
- package/dist/otel/__tests__/system-metrics.test.js.map +1 -0
- package/dist/otel/app-logs.d.ts +80 -0
- package/dist/otel/app-logs.js +195 -0
- package/dist/otel/app-logs.js.map +1 -0
- package/dist/otel/build-info.d.ts +2 -1
- package/dist/otel/build-info.js +3 -2
- package/dist/otel/build-info.js.map +1 -1
- package/dist/otel/events.d.ts +1 -0
- package/dist/otel/events.js +21 -12
- package/dist/otel/events.js.map +1 -1
- package/dist/otel/log-auto.d.ts +44 -0
- package/dist/otel/log-auto.js +200 -0
- package/dist/otel/log-auto.js.map +1 -0
- package/dist/otel/log-bridges.d.ts +54 -0
- package/dist/otel/log-bridges.js +307 -0
- package/dist/otel/log-bridges.js.map +1 -0
- package/dist/otel/system-metrics.d.ts +39 -0
- package/dist/otel/system-metrics.js +166 -0
- package/dist/otel/system-metrics.js.map +1 -0
- package/dist/otel/tracing.js +5 -1
- package/dist/otel/tracing.js.map +1 -1
- package/dist/protect/__tests__/protect.test.js +4 -0
- package/dist/protect/__tests__/protect.test.js.map +1 -1
- package/dist/protect/index.js +9 -4
- package/dist/protect/index.js.map +1 -1
- package/dist/rasp/__tests__/auto-http.test.d.ts +1 -0
- package/dist/rasp/__tests__/auto-http.test.js +136 -0
- package/dist/rasp/__tests__/auto-http.test.js.map +1 -0
- package/dist/rasp/__tests__/baseline.test.d.ts +1 -0
- package/dist/rasp/__tests__/baseline.test.js +86 -0
- package/dist/rasp/__tests__/baseline.test.js.map +1 -0
- package/dist/rasp/__tests__/benign-corpus.test.d.ts +1 -0
- package/dist/rasp/__tests__/benign-corpus.test.js +68 -0
- package/dist/rasp/__tests__/benign-corpus.test.js.map +1 -0
- package/dist/rasp/__tests__/capture-context.test.d.ts +1 -0
- package/dist/rasp/__tests__/capture-context.test.js +135 -0
- package/dist/rasp/__tests__/capture-context.test.js.map +1 -0
- package/dist/rasp/__tests__/code-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/code-hook.test.js +218 -0
- package/dist/rasp/__tests__/code-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/config-client.test.d.ts +1 -0
- package/dist/rasp/__tests__/config-client.test.js +283 -0
- package/dist/rasp/__tests__/config-client.test.js.map +1 -0
- package/dist/rasp/__tests__/corpus.d.ts +13 -0
- package/dist/rasp/__tests__/corpus.js +111 -0
- package/dist/rasp/__tests__/corpus.js.map +1 -0
- package/dist/rasp/__tests__/custom-rules.test.d.ts +1 -0
- package/dist/rasp/__tests__/custom-rules.test.js +276 -0
- package/dist/rasp/__tests__/custom-rules.test.js.map +1 -0
- package/dist/rasp/__tests__/db-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/db-hook.test.js +438 -0
- package/dist/rasp/__tests__/db-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/deception.test.d.ts +1 -0
- package/dist/rasp/__tests__/deception.test.js +567 -0
- package/dist/rasp/__tests__/deception.test.js.map +1 -0
- package/dist/rasp/__tests__/detect.test.d.ts +1 -0
- package/dist/rasp/__tests__/detect.test.js +276 -0
- package/dist/rasp/__tests__/detect.test.js.map +1 -0
- package/dist/rasp/__tests__/esm-exports.test.d.ts +1 -0
- package/dist/rasp/__tests__/esm-exports.test.js +20 -0
- package/dist/rasp/__tests__/esm-exports.test.js.map +1 -0
- package/dist/rasp/__tests__/fs-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/fs-hook.test.js +241 -0
- package/dist/rasp/__tests__/fs-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/hook-contracts.test.d.ts +1 -0
- package/dist/rasp/__tests__/hook-contracts.test.js +201 -0
- package/dist/rasp/__tests__/hook-contracts.test.js.map +1 -0
- package/dist/rasp/__tests__/hook-kit.d.ts +42 -0
- package/dist/rasp/__tests__/hook-kit.js +139 -0
- package/dist/rasp/__tests__/hook-kit.js.map +1 -0
- package/dist/rasp/__tests__/http-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/http-hook.test.js +250 -0
- package/dist/rasp/__tests__/http-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/iast.test.d.ts +1 -0
- package/dist/rasp/__tests__/iast.test.js +361 -0
- package/dist/rasp/__tests__/iast.test.js.map +1 -0
- package/dist/rasp/__tests__/include-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/include-hook.test.js +122 -0
- package/dist/rasp/__tests__/include-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/middleware.test.d.ts +1 -0
- package/dist/rasp/__tests__/middleware.test.js +302 -0
- package/dist/rasp/__tests__/middleware.test.js.map +1 -0
- package/dist/rasp/__tests__/process-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/process-hook.test.js +238 -0
- package/dist/rasp/__tests__/process-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/reporter.test.d.ts +1 -0
- package/dist/rasp/__tests__/reporter.test.js +114 -0
- package/dist/rasp/__tests__/reporter.test.js.map +1 -0
- package/dist/rasp/__tests__/route.test.d.ts +1 -0
- package/dist/rasp/__tests__/route.test.js +46 -0
- package/dist/rasp/__tests__/route.test.js.map +1 -0
- package/dist/rasp/__tests__/rule-expr.test.d.ts +1 -0
- package/dist/rasp/__tests__/rule-expr.test.js +151 -0
- package/dist/rasp/__tests__/rule-expr.test.js.map +1 -0
- package/dist/rasp/__tests__/rule-phases.test.d.ts +1 -0
- package/dist/rasp/__tests__/rule-phases.test.js +271 -0
- package/dist/rasp/__tests__/rule-phases.test.js.map +1 -0
- package/dist/rasp/__tests__/sinks-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/sinks-hook.test.js +177 -0
- package/dist/rasp/__tests__/sinks-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/targets.test.d.ts +1 -0
- package/dist/rasp/__tests__/targets.test.js +82 -0
- package/dist/rasp/__tests__/targets.test.js.map +1 -0
- package/dist/rasp/__tests__/template-hook.test.d.ts +1 -0
- package/dist/rasp/__tests__/template-hook.test.js +254 -0
- package/dist/rasp/__tests__/template-hook.test.js.map +1 -0
- package/dist/rasp/__tests__/upload.test.d.ts +1 -0
- package/dist/rasp/__tests__/upload.test.js +81 -0
- package/dist/rasp/__tests__/upload.test.js.map +1 -0
- package/dist/rasp/__tests__/vpatch-engine.test.d.ts +1 -0
- package/dist/rasp/__tests__/vpatch-engine.test.js +177 -0
- package/dist/rasp/__tests__/vpatch-engine.test.js.map +1 -0
- package/dist/rasp/auto-http.d.ts +6 -0
- package/dist/rasp/auto-http.js +194 -0
- package/dist/rasp/auto-http.js.map +1 -0
- package/dist/rasp/baseline.d.ts +35 -0
- package/dist/rasp/baseline.js +91 -0
- package/dist/rasp/baseline.js.map +1 -0
- package/dist/rasp/capture.d.ts +76 -0
- package/dist/rasp/capture.js +304 -0
- package/dist/rasp/capture.js.map +1 -0
- package/dist/rasp/config-client.d.ts +122 -0
- package/dist/rasp/config-client.js +337 -0
- package/dist/rasp/config-client.js.map +1 -0
- package/dist/rasp/context.d.ts +65 -0
- package/dist/rasp/context.js +68 -0
- package/dist/rasp/context.js.map +1 -0
- package/dist/rasp/custom-rules.d.ts +68 -0
- package/dist/rasp/custom-rules.js +222 -0
- package/dist/rasp/custom-rules.js.map +1 -0
- package/dist/rasp/data/default-rules.json +199 -0
- package/dist/rasp/data/default-sinks.json +4 -0
- package/dist/rasp/data/dnslog-domains.json +11 -0
- package/dist/rasp/deception.d.ts +70 -0
- package/dist/rasp/deception.js +220 -0
- package/dist/rasp/deception.js.map +1 -0
- package/dist/rasp/detect.d.ts +68 -0
- package/dist/rasp/detect.js +671 -0
- package/dist/rasp/detect.js.map +1 -0
- package/dist/rasp/engine.d.ts +107 -0
- package/dist/rasp/engine.js +330 -0
- package/dist/rasp/engine.js.map +1 -0
- package/dist/rasp/hooks/code.d.ts +20 -0
- package/dist/rasp/hooks/code.js +334 -0
- package/dist/rasp/hooks/code.js.map +1 -0
- package/dist/rasp/hooks/db.d.ts +48 -0
- package/dist/rasp/hooks/db.js +322 -0
- package/dist/rasp/hooks/db.js.map +1 -0
- package/dist/rasp/hooks/esm.d.ts +7 -0
- package/dist/rasp/hooks/esm.js +18 -0
- package/dist/rasp/hooks/esm.js.map +1 -0
- package/dist/rasp/hooks/fs.d.ts +3 -0
- package/dist/rasp/hooks/fs.js +206 -0
- package/dist/rasp/hooks/fs.js.map +1 -0
- package/dist/rasp/hooks/http.d.ts +4 -0
- package/dist/rasp/hooks/http.js +213 -0
- package/dist/rasp/hooks/http.js.map +1 -0
- package/dist/rasp/hooks/include.d.ts +9 -0
- package/dist/rasp/hooks/include.js +79 -0
- package/dist/rasp/hooks/include.js.map +1 -0
- package/dist/rasp/hooks/marks.d.ts +2 -0
- package/dist/rasp/hooks/marks.js +16 -0
- package/dist/rasp/hooks/marks.js.map +1 -0
- package/dist/rasp/hooks/process.d.ts +22 -0
- package/dist/rasp/hooks/process.js +191 -0
- package/dist/rasp/hooks/process.js.map +1 -0
- package/dist/rasp/hooks/sinks.d.ts +54 -0
- package/dist/rasp/hooks/sinks.js +329 -0
- package/dist/rasp/hooks/sinks.js.map +1 -0
- package/dist/rasp/hooks/template.d.ts +21 -0
- package/dist/rasp/hooks/template.js +152 -0
- package/dist/rasp/hooks/template.js.map +1 -0
- package/dist/rasp/iast-rules.d.ts +82 -0
- package/dist/rasp/iast-rules.js +240 -0
- package/dist/rasp/iast-rules.js.map +1 -0
- package/dist/rasp/iast-scope.d.ts +29 -0
- package/dist/rasp/iast-scope.js +54 -0
- package/dist/rasp/iast-scope.js.map +1 -0
- package/dist/rasp/iast.d.ts +81 -0
- package/dist/rasp/iast.js +272 -0
- package/dist/rasp/iast.js.map +1 -0
- package/dist/rasp/index.d.ts +52 -0
- package/dist/rasp/index.js +280 -0
- package/dist/rasp/index.js.map +1 -0
- package/dist/rasp/lexers.d.ts +32 -0
- package/dist/rasp/lexers.js +444 -0
- package/dist/rasp/lexers.js.map +1 -0
- package/dist/rasp/middleware.d.ts +155 -0
- package/dist/rasp/middleware.js +568 -0
- package/dist/rasp/middleware.js.map +1 -0
- package/dist/rasp/model-output.d.ts +14 -0
- package/dist/rasp/model-output.js +107 -0
- package/dist/rasp/model-output.js.map +1 -0
- package/dist/rasp/normalize.d.ts +23 -0
- package/dist/rasp/normalize.js +140 -0
- package/dist/rasp/normalize.js.map +1 -0
- package/dist/rasp/reporter.d.ts +60 -0
- package/dist/rasp/reporter.js +230 -0
- package/dist/rasp/reporter.js.map +1 -0
- package/dist/rasp/rule-expr.d.ts +236 -0
- package/dist/rasp/rule-expr.js +1253 -0
- package/dist/rasp/rule-expr.js.map +1 -0
- package/dist/rasp/template.d.ts +53 -0
- package/dist/rasp/template.js +336 -0
- package/dist/rasp/template.js.map +1 -0
- package/dist/rasp/types.d.ts +110 -0
- package/dist/rasp/types.js +51 -0
- package/dist/rasp/types.js.map +1 -0
- package/dist/rasp/vpatch.d.ts +55 -0
- package/dist/rasp/vpatch.js +120 -0
- package/dist/rasp/vpatch.js.map +1 -0
- package/dist/register.js +52 -11
- package/dist/register.js.map +1 -1
- package/dist/semantic-convention.d.ts +2 -0
- package/dist/semantic-convention.js +5 -0
- package/dist/semantic-convention.js.map +1 -1
- package/dist/types.d.ts +75 -1
- package/dist/zero-code.d.ts +8 -0
- package/dist/zero-code.js +38 -0
- package/dist/zero-code.js.map +1 -0
- package/package.json +18 -2
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Custom RASP rule expressions (sdk/rasp/README.md "Custom rules").
|
|
4
|
+
*
|
|
5
|
+
* A small, sandboxed, CEL-style expression language: a fixed set of fields,
|
|
6
|
+
* string literals, comparison, `in`, a handful of string methods, a safe
|
|
7
|
+
* regular-expression subset matched in linear time, and CIDR matching,
|
|
8
|
+
* joined by `&&`, `||` and `!`. No loops, no function definitions, no I/O,
|
|
9
|
+
* nothing outside the fields. Expressions are parsed and type-checked once
|
|
10
|
+
* and evaluated as data; nothing downloaded is ever run as code.
|
|
11
|
+
*
|
|
12
|
+
* This file is shared: `sdk/typescript/src/rasp/rule-expr.ts` and
|
|
13
|
+
* `src/client/src/lib/security/rasp/rule-expr.ts` are byte-identical (a test
|
|
14
|
+
* in each tree says so), so the platform accepts exactly what the SDK runs.
|
|
15
|
+
* It imports nothing.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.DEFAULT_RULE_BUDGET = exports.COLLECTOR_RULE_FIELDS = exports.RULE_FIELDS = exports.RULE_EXPR_LIMITS = void 0;
|
|
19
|
+
exports.ruleContextFields = ruleContextFields;
|
|
20
|
+
exports.utf8Length = utf8Length;
|
|
21
|
+
exports.compileRuleExpr = compileRuleExpr;
|
|
22
|
+
exports.evaluateRuleExpr = evaluateRuleExpr;
|
|
23
|
+
exports.compileRegex = compileRegex;
|
|
24
|
+
exports.isSafeRegex = isSafeRegex;
|
|
25
|
+
exports.parseIpBytes = parseIpBytes;
|
|
26
|
+
exports.parseCidr = parseCidr;
|
|
27
|
+
/** Hard limits, identical on the platform and in every SDK. */
|
|
28
|
+
exports.RULE_EXPR_LIMITS = Object.freeze({
|
|
29
|
+
/** UTF-8 bytes of the whole expression. */
|
|
30
|
+
expressionBytes: 2048,
|
|
31
|
+
/** AST depth (a leaf is 1), and parenthesis nesting. */
|
|
32
|
+
depth: 12,
|
|
33
|
+
/** AST nodes. */
|
|
34
|
+
nodes: 128,
|
|
35
|
+
/** UTF-8 bytes of one string literal, after escapes. */
|
|
36
|
+
stringBytes: 1024,
|
|
37
|
+
/** Items in one `in [...]` list. */
|
|
38
|
+
listItems: 64,
|
|
39
|
+
/** `matches` calls in one expression. */
|
|
40
|
+
regexes: 8,
|
|
41
|
+
/** UTF-8 bytes of one pattern. */
|
|
42
|
+
regexBytes: 256,
|
|
43
|
+
/** Atoms of one pattern once counted repetitions are expanded. */
|
|
44
|
+
regexAtoms: 256,
|
|
45
|
+
/** The largest bound in `{n}`, `{n,}`, `{n,m}`. */
|
|
46
|
+
regexRepeat: 100,
|
|
47
|
+
/** `matches` reads at most this many code points of its subject. */
|
|
48
|
+
matchCodePoints: 4096,
|
|
49
|
+
});
|
|
50
|
+
/** The fields an expression may read (README "Custom rules: fields"). */
|
|
51
|
+
exports.RULE_FIELDS = Object.freeze({
|
|
52
|
+
"request.method": "string",
|
|
53
|
+
"request.path": "string",
|
|
54
|
+
"request.route": "string",
|
|
55
|
+
"request.ip": "string",
|
|
56
|
+
"request.headers": "map",
|
|
57
|
+
"request.query": "map",
|
|
58
|
+
"request.body": "map",
|
|
59
|
+
"input.value": "string",
|
|
60
|
+
"input.key": "string",
|
|
61
|
+
"input.source": "string",
|
|
62
|
+
"sink.name": "string",
|
|
63
|
+
"attack.type": "string",
|
|
64
|
+
"attack.rule": "string",
|
|
65
|
+
"app.name": "string",
|
|
66
|
+
});
|
|
67
|
+
/** The fields of each collector context. None is a map. */
|
|
68
|
+
exports.COLLECTOR_RULE_FIELDS = Object.freeze({
|
|
69
|
+
agent_tool: Object.freeze({
|
|
70
|
+
"tool.name": "string",
|
|
71
|
+
"tool.command": "string",
|
|
72
|
+
"tool.path": "string",
|
|
73
|
+
"agent.name": "string",
|
|
74
|
+
"user.id": "string",
|
|
75
|
+
"device.os": "string",
|
|
76
|
+
}),
|
|
77
|
+
ai_proxy: Object.freeze({
|
|
78
|
+
"ai.provider": "string",
|
|
79
|
+
"ai.model": "string",
|
|
80
|
+
"ai.tokens": "string",
|
|
81
|
+
"user.id": "string",
|
|
82
|
+
"device.os": "string",
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
/** The fields an expression may read in a context, or null for one this parser does not know. */
|
|
86
|
+
function ruleContextFields(context) {
|
|
87
|
+
if (context === "app")
|
|
88
|
+
return exports.RULE_FIELDS;
|
|
89
|
+
return Object.prototype.hasOwnProperty.call(exports.COLLECTOR_RULE_FIELDS, context) ? exports.COLLECTOR_RULE_FIELDS[context] : null;
|
|
90
|
+
}
|
|
91
|
+
class ExprError extends Error {
|
|
92
|
+
constructor(code, pos) {
|
|
93
|
+
super(code);
|
|
94
|
+
this.code = code;
|
|
95
|
+
this.pos = pos;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** UTF-8 length of a JS string (a lone surrogate counts 3, as its U+FFFD replacement). */
|
|
99
|
+
function utf8Length(s) {
|
|
100
|
+
let n = 0;
|
|
101
|
+
for (let i = 0; i < s.length; i++) {
|
|
102
|
+
const c = s.charCodeAt(i);
|
|
103
|
+
if (c < 0x80)
|
|
104
|
+
n += 1;
|
|
105
|
+
else if (c < 0x800)
|
|
106
|
+
n += 2;
|
|
107
|
+
else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length && s.charCodeAt(i + 1) >= 0xdc00 && s.charCodeAt(i + 1) <= 0xdfff) {
|
|
108
|
+
n += 4;
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
n += 3;
|
|
113
|
+
}
|
|
114
|
+
return n;
|
|
115
|
+
}
|
|
116
|
+
/** The code points of a string, at most `max`. */
|
|
117
|
+
function codePoints(s, max) {
|
|
118
|
+
const out = [];
|
|
119
|
+
for (let i = 0; i < s.length && out.length < max;) {
|
|
120
|
+
const c = s.codePointAt(i);
|
|
121
|
+
out.push(c);
|
|
122
|
+
i += c > 0xffff ? 2 : 1;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
const OPS2 = ["==", "!=", "<=", ">=", "&&", "||"];
|
|
127
|
+
const OPS1 = "()[],.<>!";
|
|
128
|
+
const isIdentStart = (c) => (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_";
|
|
129
|
+
const isIdentPart = (c) => isIdentStart(c) || (c >= "0" && c <= "9");
|
|
130
|
+
const HEX = /^[0-9A-Fa-f]{4}$/;
|
|
131
|
+
function lex(src) {
|
|
132
|
+
const toks = [];
|
|
133
|
+
let i = 0;
|
|
134
|
+
while (i < src.length) {
|
|
135
|
+
const c = src[i];
|
|
136
|
+
if (c === " " || c === "\t" || c === "\n" || c === "\r") {
|
|
137
|
+
i++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const start = i;
|
|
141
|
+
const raw = (c === "r" || c === "R") && (src[i + 1] === '"' || src[i + 1] === "'");
|
|
142
|
+
if (c === '"' || c === "'" || raw) {
|
|
143
|
+
if (raw)
|
|
144
|
+
i++;
|
|
145
|
+
const quote = src[i];
|
|
146
|
+
i++;
|
|
147
|
+
let v = "";
|
|
148
|
+
for (;;) {
|
|
149
|
+
if (i >= src.length)
|
|
150
|
+
throw new ExprError("syntax", start);
|
|
151
|
+
const d = src[i];
|
|
152
|
+
if (d === quote) {
|
|
153
|
+
i++;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (d.charCodeAt(0) < 0x20)
|
|
157
|
+
throw new ExprError("syntax", i);
|
|
158
|
+
if (d === "\\" && !raw) {
|
|
159
|
+
const e = src[i + 1];
|
|
160
|
+
if (e === "\\" || e === '"' || e === "'")
|
|
161
|
+
v += e;
|
|
162
|
+
else if (e === "n")
|
|
163
|
+
v += "\n";
|
|
164
|
+
else if (e === "r")
|
|
165
|
+
v += "\r";
|
|
166
|
+
else if (e === "t")
|
|
167
|
+
v += "\t";
|
|
168
|
+
else if (e === "u") {
|
|
169
|
+
const h = src.slice(i + 2, i + 6);
|
|
170
|
+
if (!HEX.test(h))
|
|
171
|
+
throw new ExprError("syntax", i);
|
|
172
|
+
const code = parseInt(h, 16);
|
|
173
|
+
if (code >= 0xd800 && code <= 0xdfff)
|
|
174
|
+
throw new ExprError("syntax", i);
|
|
175
|
+
v += String.fromCharCode(code);
|
|
176
|
+
i += 4;
|
|
177
|
+
}
|
|
178
|
+
else
|
|
179
|
+
throw new ExprError("syntax", i);
|
|
180
|
+
i += 2;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
v += d;
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
if (utf8Length(v) > exports.RULE_EXPR_LIMITS.stringBytes)
|
|
187
|
+
throw new ExprError("string_too_long", start);
|
|
188
|
+
toks.push({ t: "str", v, pos: start });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (isIdentStart(c)) {
|
|
192
|
+
while (i < src.length && isIdentPart(src[i]))
|
|
193
|
+
i++;
|
|
194
|
+
toks.push({ t: "ident", v: src.slice(start, i), pos: start });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const two = src.slice(i, i + 2);
|
|
198
|
+
if (OPS2.includes(two)) {
|
|
199
|
+
toks.push({ t: "op", v: two, pos: start });
|
|
200
|
+
i += 2;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (OPS1.includes(c)) {
|
|
204
|
+
toks.push({ t: "op", v: c, pos: start });
|
|
205
|
+
i++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
throw new ExprError("syntax", i);
|
|
209
|
+
}
|
|
210
|
+
toks.push({ t: "eof", v: "", pos: src.length });
|
|
211
|
+
return toks;
|
|
212
|
+
}
|
|
213
|
+
// ---- parser -----------------------------------------------------------------
|
|
214
|
+
const KEYWORDS = new Set(["true", "false", "null", "in"]);
|
|
215
|
+
const STR_METHODS = new Set(["startsWith", "endsWith", "contains"]);
|
|
216
|
+
const METHODS = new Set(["startsWith", "endsWith", "contains", "lowerAscii", "matches", "inCidr"]);
|
|
217
|
+
class Parser {
|
|
218
|
+
constructor(toks, fields = exports.RULE_FIELDS) {
|
|
219
|
+
this.toks = toks;
|
|
220
|
+
this.fields = fields;
|
|
221
|
+
this.i = 0;
|
|
222
|
+
this.nodes = 0;
|
|
223
|
+
/** Parenthesis nesting, and nested `!` (each adds AST depth): both bounded while parsing. */
|
|
224
|
+
this.parens = 0;
|
|
225
|
+
this.nots = 0;
|
|
226
|
+
this.regexes = 0;
|
|
227
|
+
}
|
|
228
|
+
peek(o = 0) {
|
|
229
|
+
return this.toks[Math.min(this.i + o, this.toks.length - 1)];
|
|
230
|
+
}
|
|
231
|
+
next() {
|
|
232
|
+
const t = this.peek();
|
|
233
|
+
if (this.i < this.toks.length - 1)
|
|
234
|
+
this.i++;
|
|
235
|
+
return t;
|
|
236
|
+
}
|
|
237
|
+
isOp(v, o = 0) {
|
|
238
|
+
const t = this.peek(o);
|
|
239
|
+
return t.t === "op" && t.v === v;
|
|
240
|
+
}
|
|
241
|
+
expectOp(v) {
|
|
242
|
+
const t = this.next();
|
|
243
|
+
if (t.t !== "op" || t.v !== v)
|
|
244
|
+
throw new ExprError("syntax", t.pos);
|
|
245
|
+
return t;
|
|
246
|
+
}
|
|
247
|
+
count(n, pos) {
|
|
248
|
+
this.nodes += n;
|
|
249
|
+
if (this.nodes > exports.RULE_EXPR_LIMITS.nodes)
|
|
250
|
+
throw new ExprError("nodes", pos);
|
|
251
|
+
}
|
|
252
|
+
parse() {
|
|
253
|
+
const n = this.or();
|
|
254
|
+
const t = this.peek();
|
|
255
|
+
if (t.t !== "eof")
|
|
256
|
+
throw new ExprError("syntax", t.pos);
|
|
257
|
+
return n;
|
|
258
|
+
}
|
|
259
|
+
or() {
|
|
260
|
+
const pos = this.peek().pos;
|
|
261
|
+
const xs = [this.and()];
|
|
262
|
+
while (this.isOp("||")) {
|
|
263
|
+
this.next();
|
|
264
|
+
xs.push(this.and());
|
|
265
|
+
}
|
|
266
|
+
if (xs.length === 1)
|
|
267
|
+
return xs[0];
|
|
268
|
+
this.count(1, pos);
|
|
269
|
+
return { k: "or", xs };
|
|
270
|
+
}
|
|
271
|
+
and() {
|
|
272
|
+
const pos = this.peek().pos;
|
|
273
|
+
const xs = [this.relation()];
|
|
274
|
+
while (this.isOp("&&")) {
|
|
275
|
+
this.next();
|
|
276
|
+
xs.push(this.relation());
|
|
277
|
+
}
|
|
278
|
+
if (xs.length === 1)
|
|
279
|
+
return xs[0];
|
|
280
|
+
this.count(1, pos);
|
|
281
|
+
return { k: "and", xs };
|
|
282
|
+
}
|
|
283
|
+
relation() {
|
|
284
|
+
const l = this.unary();
|
|
285
|
+
const t = this.peek();
|
|
286
|
+
if (t.t === "op" && (t.v === "==" || t.v === "!=" || t.v === "<" || t.v === "<=" || t.v === ">" || t.v === ">=")) {
|
|
287
|
+
this.next();
|
|
288
|
+
const r = this.unary();
|
|
289
|
+
this.count(1, t.pos);
|
|
290
|
+
return { k: "cmp", op: t.v, l, r };
|
|
291
|
+
}
|
|
292
|
+
if (t.t === "ident" && t.v === "in") {
|
|
293
|
+
this.next();
|
|
294
|
+
this.expectOp("[");
|
|
295
|
+
const items = [];
|
|
296
|
+
while (!this.isOp("]")) {
|
|
297
|
+
const s = this.next();
|
|
298
|
+
if (s.t !== "str")
|
|
299
|
+
throw new ExprError("syntax", s.pos);
|
|
300
|
+
items.push(s.v);
|
|
301
|
+
if (items.length > exports.RULE_EXPR_LIMITS.listItems)
|
|
302
|
+
throw new ExprError("list_too_long", s.pos);
|
|
303
|
+
if (this.isOp(","))
|
|
304
|
+
this.next();
|
|
305
|
+
else if (!this.isOp("]"))
|
|
306
|
+
throw new ExprError("syntax", this.peek().pos);
|
|
307
|
+
}
|
|
308
|
+
this.next();
|
|
309
|
+
this.count(2 + items.length, t.pos);
|
|
310
|
+
return { k: "in", l, items };
|
|
311
|
+
}
|
|
312
|
+
return l;
|
|
313
|
+
}
|
|
314
|
+
unary() {
|
|
315
|
+
const t = this.peek();
|
|
316
|
+
if (this.isOp("!")) {
|
|
317
|
+
this.next();
|
|
318
|
+
if (++this.nots > exports.RULE_EXPR_LIMITS.depth)
|
|
319
|
+
throw new ExprError("depth", t.pos);
|
|
320
|
+
const x = this.unary();
|
|
321
|
+
this.nots--;
|
|
322
|
+
this.count(1, t.pos);
|
|
323
|
+
return { k: "not", x };
|
|
324
|
+
}
|
|
325
|
+
return this.member();
|
|
326
|
+
}
|
|
327
|
+
member() {
|
|
328
|
+
let n = this.primary();
|
|
329
|
+
while (this.isOp(".")) {
|
|
330
|
+
this.next();
|
|
331
|
+
const name = this.next();
|
|
332
|
+
if (name.t !== "ident")
|
|
333
|
+
throw new ExprError("syntax", name.pos);
|
|
334
|
+
if (!this.isOp("("))
|
|
335
|
+
throw new ExprError("syntax", this.peek().pos);
|
|
336
|
+
this.next();
|
|
337
|
+
let arg;
|
|
338
|
+
if (this.peek().t === "str")
|
|
339
|
+
arg = this.next().v;
|
|
340
|
+
this.expectOp(")");
|
|
341
|
+
if (!METHODS.has(name.v))
|
|
342
|
+
throw new ExprError("unknown_method", name.pos);
|
|
343
|
+
if (name.v === "lowerAscii") {
|
|
344
|
+
if (arg !== undefined)
|
|
345
|
+
throw new ExprError("type", name.pos);
|
|
346
|
+
this.count(1, name.pos);
|
|
347
|
+
n = { k: "lower", t: n };
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (arg === undefined)
|
|
351
|
+
throw new ExprError("type", name.pos);
|
|
352
|
+
this.count(2, name.pos);
|
|
353
|
+
if (STR_METHODS.has(name.v))
|
|
354
|
+
n = { k: "str_method", m: name.v, t: n, arg };
|
|
355
|
+
else if (name.v === "matches") {
|
|
356
|
+
this.regexes++;
|
|
357
|
+
if (this.regexes > exports.RULE_EXPR_LIMITS.regexes)
|
|
358
|
+
throw new ExprError("too_many_regexes", name.pos);
|
|
359
|
+
const prog = compileRegex(arg);
|
|
360
|
+
if (!prog)
|
|
361
|
+
throw new ExprError("regex", name.pos);
|
|
362
|
+
n = { k: "matches", t: n, prog };
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
const net = parseCidr(arg);
|
|
366
|
+
if (!net)
|
|
367
|
+
throw new ExprError("cidr", name.pos);
|
|
368
|
+
n = { k: "cidr", t: n, net };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return n;
|
|
372
|
+
}
|
|
373
|
+
primary() {
|
|
374
|
+
const t = this.next();
|
|
375
|
+
if (t.t === "str") {
|
|
376
|
+
this.count(1, t.pos);
|
|
377
|
+
return { k: "str", v: t.v };
|
|
378
|
+
}
|
|
379
|
+
if (t.t === "op" && t.v === "(") {
|
|
380
|
+
if (++this.parens > exports.RULE_EXPR_LIMITS.depth)
|
|
381
|
+
throw new ExprError("depth", t.pos);
|
|
382
|
+
const n = this.or();
|
|
383
|
+
this.parens--;
|
|
384
|
+
this.expectOp(")");
|
|
385
|
+
return n;
|
|
386
|
+
}
|
|
387
|
+
if (t.t === "ident") {
|
|
388
|
+
if (t.v === "true" || t.v === "false") {
|
|
389
|
+
this.count(1, t.pos);
|
|
390
|
+
return { k: "bool", v: t.v === "true" };
|
|
391
|
+
}
|
|
392
|
+
if (t.v === "null") {
|
|
393
|
+
this.count(1, t.pos);
|
|
394
|
+
return { k: "null" };
|
|
395
|
+
}
|
|
396
|
+
if (KEYWORDS.has(t.v))
|
|
397
|
+
throw new ExprError("syntax", t.pos);
|
|
398
|
+
// A dotted field name; a segment followed by `(` is a method call.
|
|
399
|
+
let name = t.v;
|
|
400
|
+
while (this.isOp(".") && this.peek(1).t === "ident" && !(this.peek(2).t === "op" && this.peek(2).v === "(")) {
|
|
401
|
+
this.next();
|
|
402
|
+
name += `.${this.next().v}`;
|
|
403
|
+
}
|
|
404
|
+
const type = Object.prototype.hasOwnProperty.call(this.fields, name) ? this.fields[name] : undefined;
|
|
405
|
+
if (!type)
|
|
406
|
+
throw new ExprError("unknown_field", t.pos);
|
|
407
|
+
let key;
|
|
408
|
+
if (this.isOp("[")) {
|
|
409
|
+
const open = this.next();
|
|
410
|
+
const s = this.next();
|
|
411
|
+
if (s.t !== "str")
|
|
412
|
+
throw new ExprError("syntax", s.pos);
|
|
413
|
+
this.expectOp("]");
|
|
414
|
+
if (type !== "map")
|
|
415
|
+
throw new ExprError("type", open.pos);
|
|
416
|
+
// Header names are case-insensitive: ASCII lower-cased here, as the SDKs record them.
|
|
417
|
+
key = name === "request.headers" ? asciiLower(s.v) : s.v;
|
|
418
|
+
}
|
|
419
|
+
else if (type === "map")
|
|
420
|
+
throw new ExprError("type", t.pos);
|
|
421
|
+
this.count(1, t.pos);
|
|
422
|
+
return key === undefined ? { k: "field", name } : { k: "field", name, key };
|
|
423
|
+
}
|
|
424
|
+
throw new ExprError("syntax", t.pos);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function typeOf(n) {
|
|
428
|
+
switch (n.k) {
|
|
429
|
+
case "str":
|
|
430
|
+
return "string";
|
|
431
|
+
case "bool":
|
|
432
|
+
return "bool";
|
|
433
|
+
case "null":
|
|
434
|
+
return "null";
|
|
435
|
+
case "field":
|
|
436
|
+
return "string";
|
|
437
|
+
case "not":
|
|
438
|
+
if (typeOf(n.x) !== "bool")
|
|
439
|
+
throw new ExprError("type", 0);
|
|
440
|
+
return "bool";
|
|
441
|
+
case "and":
|
|
442
|
+
case "or":
|
|
443
|
+
for (const x of n.xs)
|
|
444
|
+
if (typeOf(x) !== "bool")
|
|
445
|
+
throw new ExprError("type", 0);
|
|
446
|
+
return "bool";
|
|
447
|
+
case "cmp": {
|
|
448
|
+
const l = typeOf(n.l);
|
|
449
|
+
const r = typeOf(n.r);
|
|
450
|
+
if (n.op === "==" || n.op === "!=") {
|
|
451
|
+
const strish = (t) => t === "string" || t === "null";
|
|
452
|
+
if (!((strish(l) && strish(r)) || (l === "bool" && r === "bool")))
|
|
453
|
+
throw new ExprError("type", 0);
|
|
454
|
+
}
|
|
455
|
+
else if (l !== "string" || r !== "string")
|
|
456
|
+
throw new ExprError("type", 0);
|
|
457
|
+
return "bool";
|
|
458
|
+
}
|
|
459
|
+
case "in":
|
|
460
|
+
case "str_method":
|
|
461
|
+
case "matches":
|
|
462
|
+
case "cidr":
|
|
463
|
+
if (typeOf(n.k === "in" ? n.l : n.t) !== "string")
|
|
464
|
+
throw new ExprError("type", 0);
|
|
465
|
+
return "bool";
|
|
466
|
+
case "lower":
|
|
467
|
+
if (typeOf(n.t) !== "string")
|
|
468
|
+
throw new ExprError("type", 0);
|
|
469
|
+
return "string";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function depthOf(n) {
|
|
473
|
+
switch (n.k) {
|
|
474
|
+
case "not":
|
|
475
|
+
return 1 + depthOf(n.x);
|
|
476
|
+
case "and":
|
|
477
|
+
case "or":
|
|
478
|
+
return 1 + Math.max(...n.xs.map(depthOf));
|
|
479
|
+
case "cmp":
|
|
480
|
+
return 1 + Math.max(depthOf(n.l), depthOf(n.r));
|
|
481
|
+
case "in":
|
|
482
|
+
return 1 + Math.max(depthOf(n.l), 2);
|
|
483
|
+
case "str_method":
|
|
484
|
+
case "matches":
|
|
485
|
+
case "cidr":
|
|
486
|
+
return 1 + Math.max(depthOf(n.t), 1);
|
|
487
|
+
case "lower":
|
|
488
|
+
return 1 + depthOf(n.t);
|
|
489
|
+
default:
|
|
490
|
+
return 1;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function collectFields(n, out) {
|
|
494
|
+
switch (n.k) {
|
|
495
|
+
case "field":
|
|
496
|
+
out.add(n.name);
|
|
497
|
+
return;
|
|
498
|
+
case "not":
|
|
499
|
+
collectFields(n.x, out);
|
|
500
|
+
return;
|
|
501
|
+
case "and":
|
|
502
|
+
case "or":
|
|
503
|
+
n.xs.forEach((x) => collectFields(x, out));
|
|
504
|
+
return;
|
|
505
|
+
case "cmp":
|
|
506
|
+
collectFields(n.l, out);
|
|
507
|
+
collectFields(n.r, out);
|
|
508
|
+
return;
|
|
509
|
+
case "in":
|
|
510
|
+
collectFields(n.l, out);
|
|
511
|
+
return;
|
|
512
|
+
case "str_method":
|
|
513
|
+
case "matches":
|
|
514
|
+
case "cidr":
|
|
515
|
+
case "lower":
|
|
516
|
+
collectFields(n.t, out);
|
|
517
|
+
return;
|
|
518
|
+
default:
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Parses and type-checks an expression. Every limit is checked here; an
|
|
524
|
+
* expression that compiles is safe to evaluate on any input.
|
|
525
|
+
*/
|
|
526
|
+
function compileRuleExpr(src, context = "app") {
|
|
527
|
+
try {
|
|
528
|
+
const fieldsOf = ruleContextFields(context);
|
|
529
|
+
if (!fieldsOf)
|
|
530
|
+
return { ok: false, error: { code: "context", pos: 0 } };
|
|
531
|
+
if (typeof src !== "string" || src.trim() === "")
|
|
532
|
+
return { ok: false, error: { code: "empty", pos: 0 } };
|
|
533
|
+
if (utf8Length(src) > exports.RULE_EXPR_LIMITS.expressionBytes)
|
|
534
|
+
return { ok: false, error: { code: "too_long", pos: 0 } };
|
|
535
|
+
const p = new Parser(lex(src), fieldsOf);
|
|
536
|
+
const ast = p.parse();
|
|
537
|
+
if (typeOf(ast) !== "bool")
|
|
538
|
+
throw new ExprError("type", 0);
|
|
539
|
+
if (depthOf(ast) > exports.RULE_EXPR_LIMITS.depth)
|
|
540
|
+
throw new ExprError("depth", 0);
|
|
541
|
+
const set = new Set();
|
|
542
|
+
collectFields(ast, set);
|
|
543
|
+
const fields = Array.from(set).sort();
|
|
544
|
+
const phase = fields.some((f) => f.startsWith("sink.") || f.startsWith("attack.")) ? "finding" : "request";
|
|
545
|
+
return { ok: true, expr: { ast, fields, phase, usesInput: fields.some((f) => f.startsWith("input.")), usesBody: set.has("request.body"), context } };
|
|
546
|
+
}
|
|
547
|
+
catch (e) {
|
|
548
|
+
if (e instanceof ExprError)
|
|
549
|
+
return { ok: false, error: { code: e.code, pos: e.pos } };
|
|
550
|
+
return { ok: false, error: { code: "syntax", pos: 0 } };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
exports.DEFAULT_RULE_BUDGET = 4000000;
|
|
554
|
+
class BudgetExhausted extends Error {
|
|
555
|
+
}
|
|
556
|
+
const asciiLower = (s) => s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));
|
|
557
|
+
function own(map, key) {
|
|
558
|
+
if (!map || typeof map !== "object" || !Object.prototype.hasOwnProperty.call(map, key))
|
|
559
|
+
return null;
|
|
560
|
+
const v = map[key];
|
|
561
|
+
return typeof v === "string" ? v : null;
|
|
562
|
+
}
|
|
563
|
+
const str = (v) => (typeof v === "string" ? v : null);
|
|
564
|
+
/** Compares two strings by Unicode code point. */
|
|
565
|
+
function cmpCodePoints(a, b) {
|
|
566
|
+
let i = 0;
|
|
567
|
+
let j = 0;
|
|
568
|
+
while (i < a.length && j < b.length) {
|
|
569
|
+
const x = a.codePointAt(i);
|
|
570
|
+
const y = b.codePointAt(j);
|
|
571
|
+
if (x !== y)
|
|
572
|
+
return x < y ? -1 : 1;
|
|
573
|
+
i += x > 0xffff ? 2 : 1;
|
|
574
|
+
j += y > 0xffff ? 2 : 1;
|
|
575
|
+
}
|
|
576
|
+
return i < a.length ? 1 : j < b.length ? -1 : 0;
|
|
577
|
+
}
|
|
578
|
+
function field(n, s, input) {
|
|
579
|
+
const r = s.request && typeof s.request === "object" ? s.request : null;
|
|
580
|
+
switch (n.name) {
|
|
581
|
+
case "request.method":
|
|
582
|
+
return str(r?.method);
|
|
583
|
+
case "request.path":
|
|
584
|
+
return str(r?.path);
|
|
585
|
+
case "request.route":
|
|
586
|
+
return str(r?.route);
|
|
587
|
+
case "request.ip":
|
|
588
|
+
return str(r?.ip);
|
|
589
|
+
case "request.headers":
|
|
590
|
+
return own(r?.headers, n.key);
|
|
591
|
+
case "request.query":
|
|
592
|
+
return own(r?.query, n.key);
|
|
593
|
+
case "request.body":
|
|
594
|
+
return own(r?.body, n.key);
|
|
595
|
+
case "input.value":
|
|
596
|
+
return str(input?.value);
|
|
597
|
+
case "input.key":
|
|
598
|
+
return str(input?.key);
|
|
599
|
+
case "input.source":
|
|
600
|
+
return str(input?.source);
|
|
601
|
+
case "sink.name":
|
|
602
|
+
return str(s.sink);
|
|
603
|
+
case "attack.type":
|
|
604
|
+
return str(s.attack?.type);
|
|
605
|
+
case "attack.rule":
|
|
606
|
+
return str(s.attack?.rule);
|
|
607
|
+
case "app.name":
|
|
608
|
+
return str(s.app);
|
|
609
|
+
default:
|
|
610
|
+
return collectorField(n.name, s);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function collectorField(name, s) {
|
|
614
|
+
const c = s.collector && typeof s.collector === "object" ? s.collector : null;
|
|
615
|
+
if (!c)
|
|
616
|
+
return null;
|
|
617
|
+
switch (name) {
|
|
618
|
+
case "tool.name":
|
|
619
|
+
return str(c.tool?.name);
|
|
620
|
+
case "tool.command":
|
|
621
|
+
return str(c.tool?.command);
|
|
622
|
+
case "tool.path":
|
|
623
|
+
return str(c.tool?.path);
|
|
624
|
+
case "agent.name":
|
|
625
|
+
return str(c.agent?.name);
|
|
626
|
+
case "ai.provider":
|
|
627
|
+
return str(c.ai?.provider);
|
|
628
|
+
case "ai.model":
|
|
629
|
+
return str(c.ai?.model);
|
|
630
|
+
case "ai.tokens":
|
|
631
|
+
return str(c.ai?.tokens);
|
|
632
|
+
case "user.id":
|
|
633
|
+
return str(c.user?.id);
|
|
634
|
+
case "device.os":
|
|
635
|
+
return str(c.device?.os);
|
|
636
|
+
default:
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function ev(n, s, input, budget) {
|
|
641
|
+
switch (n.k) {
|
|
642
|
+
case "str":
|
|
643
|
+
case "bool":
|
|
644
|
+
return n.v;
|
|
645
|
+
case "null":
|
|
646
|
+
return null;
|
|
647
|
+
case "field":
|
|
648
|
+
return field(n, s, input);
|
|
649
|
+
case "not":
|
|
650
|
+
return !ev(n.x, s, input, budget);
|
|
651
|
+
case "and":
|
|
652
|
+
for (const x of n.xs)
|
|
653
|
+
if (!ev(x, s, input, budget))
|
|
654
|
+
return false;
|
|
655
|
+
return true;
|
|
656
|
+
case "or":
|
|
657
|
+
for (const x of n.xs)
|
|
658
|
+
if (ev(x, s, input, budget))
|
|
659
|
+
return true;
|
|
660
|
+
return false;
|
|
661
|
+
case "cmp": {
|
|
662
|
+
const l = ev(n.l, s, input, budget);
|
|
663
|
+
const r = ev(n.r, s, input, budget);
|
|
664
|
+
if (n.op === "==")
|
|
665
|
+
return l === r;
|
|
666
|
+
if (n.op === "!=")
|
|
667
|
+
return l !== r;
|
|
668
|
+
if (typeof l !== "string" || typeof r !== "string")
|
|
669
|
+
return false;
|
|
670
|
+
const c = cmpCodePoints(l, r);
|
|
671
|
+
return n.op === "<" ? c < 0 : n.op === "<=" ? c <= 0 : n.op === ">" ? c > 0 : c >= 0;
|
|
672
|
+
}
|
|
673
|
+
case "in": {
|
|
674
|
+
const l = ev(n.l, s, input, budget);
|
|
675
|
+
return typeof l === "string" && n.items.includes(l);
|
|
676
|
+
}
|
|
677
|
+
case "str_method": {
|
|
678
|
+
const t = ev(n.t, s, input, budget);
|
|
679
|
+
if (typeof t !== "string")
|
|
680
|
+
return false;
|
|
681
|
+
return n.m === "startsWith" ? t.startsWith(n.arg) : n.m === "endsWith" ? t.endsWith(n.arg) : t.includes(n.arg);
|
|
682
|
+
}
|
|
683
|
+
case "lower": {
|
|
684
|
+
const t = ev(n.t, s, input, budget);
|
|
685
|
+
return typeof t === "string" ? asciiLower(t) : null;
|
|
686
|
+
}
|
|
687
|
+
case "matches": {
|
|
688
|
+
const t = ev(n.t, s, input, budget);
|
|
689
|
+
return typeof t === "string" && runRegex(n.prog, codePoints(t, exports.RULE_EXPR_LIMITS.matchCodePoints), budget);
|
|
690
|
+
}
|
|
691
|
+
case "cidr": {
|
|
692
|
+
const t = ev(n.t, s, input, budget);
|
|
693
|
+
return typeof t === "string" && inCidr(t, n.net);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Evaluates a compiled expression. An expression that reads `input.*` is
|
|
699
|
+
* evaluated once per input, in order, and matches when any input makes it
|
|
700
|
+
* true (with no inputs, once with the input fields null). Never throws: an
|
|
701
|
+
* evaluation that runs out of budget, or fails in any way, is no match.
|
|
702
|
+
*/
|
|
703
|
+
function evaluateRuleExpr(expr, subject, budget = { steps: exports.DEFAULT_RULE_BUDGET }) {
|
|
704
|
+
try {
|
|
705
|
+
if (!expr.usesInput)
|
|
706
|
+
return { matched: ev(expr.ast, subject, null, budget) === true };
|
|
707
|
+
const inputs = Array.isArray(subject.inputs) ? subject.inputs : [];
|
|
708
|
+
if (!inputs.length)
|
|
709
|
+
return { matched: ev(expr.ast, subject, null, budget) === true };
|
|
710
|
+
for (const input of inputs) {
|
|
711
|
+
if (input && typeof input === "object" && ev(expr.ast, subject, input, budget) === true)
|
|
712
|
+
return { matched: true, input };
|
|
713
|
+
}
|
|
714
|
+
return { matched: false };
|
|
715
|
+
}
|
|
716
|
+
catch (e) {
|
|
717
|
+
return { matched: false, exhausted: e instanceof BudgetExhausted };
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const MAX_CP = 0x10ffff;
|
|
721
|
+
const DIGIT = [0x30, 0x39];
|
|
722
|
+
const WORD = [0x30, 0x39, 0x41, 0x5a, 0x5f, 0x5f, 0x61, 0x7a];
|
|
723
|
+
const SPACE = [0x09, 0x0d, 0x20, 0x20];
|
|
724
|
+
const META = "\\.^$|?*+()[]{}";
|
|
725
|
+
const ESCAPABLE = "\\.^$|?*+()[]{}/-";
|
|
726
|
+
class RegexError extends Error {
|
|
727
|
+
}
|
|
728
|
+
function complement(r) {
|
|
729
|
+
const sorted = [];
|
|
730
|
+
for (let i = 0; i < r.length; i += 2)
|
|
731
|
+
sorted.push([r[i], r[i + 1]]);
|
|
732
|
+
sorted.sort((a, b) => a[0] - b[0]);
|
|
733
|
+
const out = [];
|
|
734
|
+
let next = 0;
|
|
735
|
+
for (const [lo, hi] of sorted) {
|
|
736
|
+
if (lo > next)
|
|
737
|
+
out.push(next, lo - 1);
|
|
738
|
+
next = Math.max(next, hi + 1);
|
|
739
|
+
}
|
|
740
|
+
if (next <= MAX_CP)
|
|
741
|
+
out.push(next, MAX_CP);
|
|
742
|
+
return out;
|
|
743
|
+
}
|
|
744
|
+
class RegexParser {
|
|
745
|
+
constructor(cp) {
|
|
746
|
+
this.cp = cp;
|
|
747
|
+
this.i = 0;
|
|
748
|
+
}
|
|
749
|
+
peek() {
|
|
750
|
+
return this.cp[this.i];
|
|
751
|
+
}
|
|
752
|
+
ch(c) {
|
|
753
|
+
return this.cp[this.i] === c.charCodeAt(0);
|
|
754
|
+
}
|
|
755
|
+
parse() {
|
|
756
|
+
const n = this.alt();
|
|
757
|
+
if (this.i < this.cp.length)
|
|
758
|
+
throw new RegexError();
|
|
759
|
+
return n;
|
|
760
|
+
}
|
|
761
|
+
alt() {
|
|
762
|
+
const xs = [this.cat()];
|
|
763
|
+
while (this.ch("|")) {
|
|
764
|
+
this.i++;
|
|
765
|
+
xs.push(this.cat());
|
|
766
|
+
}
|
|
767
|
+
return xs.length === 1 ? xs[0] : { t: "alt", xs };
|
|
768
|
+
}
|
|
769
|
+
cat() {
|
|
770
|
+
const xs = [];
|
|
771
|
+
while (this.i < this.cp.length && !this.ch("|") && !this.ch(")"))
|
|
772
|
+
xs.push(this.piece());
|
|
773
|
+
return xs.length === 1 ? xs[0] : { t: "cat", xs };
|
|
774
|
+
}
|
|
775
|
+
piece() {
|
|
776
|
+
const atom = this.atom();
|
|
777
|
+
const q = this.quant();
|
|
778
|
+
if (!q)
|
|
779
|
+
return atom;
|
|
780
|
+
if (atom.t === "bol" || atom.t === "eol")
|
|
781
|
+
throw new RegexError();
|
|
782
|
+
if (hasRep(atom))
|
|
783
|
+
throw new RegexError(); // nested quantifier
|
|
784
|
+
if (atomsOf(atom) === 0)
|
|
785
|
+
throw new RegexError(); // nothing to repeat
|
|
786
|
+
return { t: "rep", x: atom, min: q.min, max: q.max };
|
|
787
|
+
}
|
|
788
|
+
num() {
|
|
789
|
+
let s = "";
|
|
790
|
+
while (this.peek() !== undefined && this.peek() >= 0x30 && this.peek() <= 0x39) {
|
|
791
|
+
s += String.fromCharCode(this.peek());
|
|
792
|
+
this.i++;
|
|
793
|
+
if (s.length > 3)
|
|
794
|
+
throw new RegexError();
|
|
795
|
+
}
|
|
796
|
+
return s ? parseInt(s, 10) : null;
|
|
797
|
+
}
|
|
798
|
+
quant() {
|
|
799
|
+
let q = null;
|
|
800
|
+
if (this.ch("*"))
|
|
801
|
+
q = { min: 0, max: -1 };
|
|
802
|
+
else if (this.ch("+"))
|
|
803
|
+
q = { min: 1, max: -1 };
|
|
804
|
+
else if (this.ch("?"))
|
|
805
|
+
q = { min: 0, max: 1 };
|
|
806
|
+
if (q)
|
|
807
|
+
this.i++;
|
|
808
|
+
else if (this.ch("{")) {
|
|
809
|
+
this.i++;
|
|
810
|
+
const min = this.num();
|
|
811
|
+
if (min === null)
|
|
812
|
+
throw new RegexError();
|
|
813
|
+
let max = min;
|
|
814
|
+
if (this.ch(",")) {
|
|
815
|
+
this.i++;
|
|
816
|
+
const m = this.num();
|
|
817
|
+
max = m === null ? -1 : m;
|
|
818
|
+
}
|
|
819
|
+
if (!this.ch("}"))
|
|
820
|
+
throw new RegexError();
|
|
821
|
+
this.i++;
|
|
822
|
+
const L = exports.RULE_EXPR_LIMITS.regexRepeat;
|
|
823
|
+
if (min > L || max > L || (max !== -1 && max < min))
|
|
824
|
+
throw new RegexError();
|
|
825
|
+
q = { min, max };
|
|
826
|
+
}
|
|
827
|
+
else
|
|
828
|
+
return null;
|
|
829
|
+
if (this.ch("?"))
|
|
830
|
+
this.i++; // lazy: the same language, so the same answer
|
|
831
|
+
if (this.ch("*") || this.ch("+") || this.ch("?") || this.ch("{"))
|
|
832
|
+
throw new RegexError();
|
|
833
|
+
return q;
|
|
834
|
+
}
|
|
835
|
+
escape() {
|
|
836
|
+
this.i++; // the backslash
|
|
837
|
+
const c = this.peek();
|
|
838
|
+
if (c === undefined)
|
|
839
|
+
throw new RegexError();
|
|
840
|
+
this.i++;
|
|
841
|
+
// Only ASCII has meaning after a backslash; a code point above U+FFFF must never alias one.
|
|
842
|
+
const e = c < 0x80 ? String.fromCharCode(c) : "";
|
|
843
|
+
if (e === "d" || e === "D")
|
|
844
|
+
return { t: "class", r: DIGIT.slice(), neg: e === "D" };
|
|
845
|
+
if (e === "w" || e === "W")
|
|
846
|
+
return { t: "class", r: WORD.slice(), neg: e === "W" };
|
|
847
|
+
if (e === "s" || e === "S")
|
|
848
|
+
return { t: "class", r: SPACE.slice(), neg: e === "S" };
|
|
849
|
+
if (e === "n")
|
|
850
|
+
return { t: "char", c: 0x0a };
|
|
851
|
+
if (e === "r")
|
|
852
|
+
return { t: "char", c: 0x0d };
|
|
853
|
+
if (e === "t")
|
|
854
|
+
return { t: "char", c: 0x09 };
|
|
855
|
+
if (e === "f")
|
|
856
|
+
return { t: "char", c: 0x0c };
|
|
857
|
+
if (e === "v")
|
|
858
|
+
return { t: "char", c: 0x0b };
|
|
859
|
+
if (e === "x" || e === "u") {
|
|
860
|
+
const n = e === "x" ? 2 : 4;
|
|
861
|
+
const digits = this.cp.slice(this.i, this.i + n);
|
|
862
|
+
if (digits.length !== n || digits.some((d) => d >= 0x80))
|
|
863
|
+
throw new RegexError();
|
|
864
|
+
const h = String.fromCharCode(...digits);
|
|
865
|
+
if (!/^[0-9A-Fa-f]+$/.test(h))
|
|
866
|
+
throw new RegexError();
|
|
867
|
+
const code = parseInt(h, 16);
|
|
868
|
+
if (code >= 0xd800 && code <= 0xdfff)
|
|
869
|
+
throw new RegexError();
|
|
870
|
+
this.i += n;
|
|
871
|
+
return { t: "char", c: code };
|
|
872
|
+
}
|
|
873
|
+
if (e && ESCAPABLE.includes(e))
|
|
874
|
+
return { t: "char", c };
|
|
875
|
+
throw new RegexError();
|
|
876
|
+
}
|
|
877
|
+
atom() {
|
|
878
|
+
const c = this.peek();
|
|
879
|
+
// Operators are ASCII; any other code point (U+1002E included) is a literal.
|
|
880
|
+
const s = c < 0x80 ? String.fromCharCode(c) : "";
|
|
881
|
+
if (s === "(") {
|
|
882
|
+
this.i++;
|
|
883
|
+
if (this.ch("?")) {
|
|
884
|
+
this.i++;
|
|
885
|
+
if (!this.ch(":"))
|
|
886
|
+
throw new RegexError(); // lookaround, named groups, flags
|
|
887
|
+
this.i++;
|
|
888
|
+
}
|
|
889
|
+
const inner = this.alt();
|
|
890
|
+
if (!this.ch(")"))
|
|
891
|
+
throw new RegexError();
|
|
892
|
+
this.i++;
|
|
893
|
+
return inner;
|
|
894
|
+
}
|
|
895
|
+
if (s === "[")
|
|
896
|
+
return this.cls();
|
|
897
|
+
if (s === ".") {
|
|
898
|
+
this.i++;
|
|
899
|
+
return { t: "any" };
|
|
900
|
+
}
|
|
901
|
+
if (s === "^") {
|
|
902
|
+
this.i++;
|
|
903
|
+
return { t: "bol" };
|
|
904
|
+
}
|
|
905
|
+
if (s === "$") {
|
|
906
|
+
this.i++;
|
|
907
|
+
return { t: "eol" };
|
|
908
|
+
}
|
|
909
|
+
if (s === "\\")
|
|
910
|
+
return this.escape();
|
|
911
|
+
if (s && META.includes(s))
|
|
912
|
+
throw new RegexError();
|
|
913
|
+
this.i++;
|
|
914
|
+
return { t: "char", c };
|
|
915
|
+
}
|
|
916
|
+
clsItem() {
|
|
917
|
+
const c = this.peek();
|
|
918
|
+
if (c === undefined)
|
|
919
|
+
throw new RegexError();
|
|
920
|
+
if (c === 0x5c)
|
|
921
|
+
return this.escape();
|
|
922
|
+
if (c === 0x5b)
|
|
923
|
+
throw new RegexError(); // unescaped [ inside a class
|
|
924
|
+
this.i++;
|
|
925
|
+
return { t: "char", c };
|
|
926
|
+
}
|
|
927
|
+
cls() {
|
|
928
|
+
this.i++; // [
|
|
929
|
+
let neg = false;
|
|
930
|
+
if (this.ch("^")) {
|
|
931
|
+
neg = true;
|
|
932
|
+
this.i++;
|
|
933
|
+
}
|
|
934
|
+
const r = [];
|
|
935
|
+
while (!this.ch("]")) {
|
|
936
|
+
if (this.i >= this.cp.length)
|
|
937
|
+
throw new RegexError();
|
|
938
|
+
const a = this.clsItem();
|
|
939
|
+
// `-` between two items is a range; at the start or right before `]` it is literal.
|
|
940
|
+
if (this.ch("-") && this.cp[this.i + 1] !== undefined && this.cp[this.i + 1] !== 0x5d) {
|
|
941
|
+
this.i++; // -
|
|
942
|
+
const b = this.clsItem();
|
|
943
|
+
if (a.t !== "char" || b.t !== "char" || a.c > b.c)
|
|
944
|
+
throw new RegexError();
|
|
945
|
+
r.push(a.c, b.c);
|
|
946
|
+
}
|
|
947
|
+
else if (a.t === "char")
|
|
948
|
+
r.push(a.c, a.c);
|
|
949
|
+
else if (a.t === "class") {
|
|
950
|
+
const parts = a.neg ? complement(a.r) : a.r;
|
|
951
|
+
r.push(...parts);
|
|
952
|
+
}
|
|
953
|
+
else
|
|
954
|
+
throw new RegexError();
|
|
955
|
+
}
|
|
956
|
+
this.i++; // ]
|
|
957
|
+
if (!r.length)
|
|
958
|
+
throw new RegexError();
|
|
959
|
+
return { t: "class", r, neg };
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
function hasRep(n) {
|
|
963
|
+
if (n.t === "rep")
|
|
964
|
+
return true;
|
|
965
|
+
if (n.t === "cat" || n.t === "alt")
|
|
966
|
+
return n.xs.some(hasRep);
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
/** Atoms once counted repetitions are expanded (README "Regular expressions"). */
|
|
970
|
+
function atomsOf(n) {
|
|
971
|
+
switch (n.t) {
|
|
972
|
+
case "char":
|
|
973
|
+
case "any":
|
|
974
|
+
case "class":
|
|
975
|
+
return 1;
|
|
976
|
+
case "bol":
|
|
977
|
+
case "eol":
|
|
978
|
+
return 0;
|
|
979
|
+
case "cat":
|
|
980
|
+
case "alt":
|
|
981
|
+
return n.xs.reduce((a, x) => a + atomsOf(x), 0);
|
|
982
|
+
case "rep": {
|
|
983
|
+
// The copies the program holds: min, then one loop (unbounded) or max - min optional ones.
|
|
984
|
+
return atomsOf(n.x) * (n.max === -1 ? n.min + 1 : n.max);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function emit(n, prog) {
|
|
989
|
+
switch (n.t) {
|
|
990
|
+
case "char":
|
|
991
|
+
prog.push({ op: "char", c: n.c });
|
|
992
|
+
return;
|
|
993
|
+
case "any":
|
|
994
|
+
prog.push({ op: "any" });
|
|
995
|
+
return;
|
|
996
|
+
case "class":
|
|
997
|
+
prog.push({ op: "class", r: n.r, neg: n.neg });
|
|
998
|
+
return;
|
|
999
|
+
case "bol":
|
|
1000
|
+
prog.push({ op: "bol" });
|
|
1001
|
+
return;
|
|
1002
|
+
case "eol":
|
|
1003
|
+
prog.push({ op: "eol" });
|
|
1004
|
+
return;
|
|
1005
|
+
case "cat":
|
|
1006
|
+
n.xs.forEach((x) => emit(x, prog));
|
|
1007
|
+
return;
|
|
1008
|
+
case "alt": {
|
|
1009
|
+
// split L1, next; L1: x0; jmp end; next: split ... ; last
|
|
1010
|
+
const jumps = [];
|
|
1011
|
+
for (let k = 0; k < n.xs.length; k++) {
|
|
1012
|
+
if (k < n.xs.length - 1) {
|
|
1013
|
+
const split = { op: "split", x: prog.length + 1, y: -1 };
|
|
1014
|
+
prog.push(split);
|
|
1015
|
+
emit(n.xs[k], prog);
|
|
1016
|
+
const j = { op: "jmp", x: -1 };
|
|
1017
|
+
prog.push(j);
|
|
1018
|
+
jumps.push(j);
|
|
1019
|
+
split.y = prog.length;
|
|
1020
|
+
}
|
|
1021
|
+
else
|
|
1022
|
+
emit(n.xs[k], prog);
|
|
1023
|
+
}
|
|
1024
|
+
for (const j of jumps)
|
|
1025
|
+
j.x = prog.length;
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
case "rep": {
|
|
1029
|
+
for (let k = 0; k < n.min; k++)
|
|
1030
|
+
emit(n.x, prog);
|
|
1031
|
+
if (n.max === -1) {
|
|
1032
|
+
// L: split body, end; body; jmp L
|
|
1033
|
+
const L = prog.length;
|
|
1034
|
+
const split = { op: "split", x: L + 1, y: -1 };
|
|
1035
|
+
prog.push(split);
|
|
1036
|
+
emit(n.x, prog);
|
|
1037
|
+
prog.push({ op: "jmp", x: L });
|
|
1038
|
+
split.y = prog.length;
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
for (let k = n.min; k < n.max; k++) {
|
|
1042
|
+
const split = { op: "split", x: prog.length + 1, y: -1 };
|
|
1043
|
+
prog.push(split);
|
|
1044
|
+
emit(n.x, prog);
|
|
1045
|
+
split.y = prog.length;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/** Checks a pattern against the safe subset and compiles it; null when refused. */
|
|
1053
|
+
function compileRegex(pattern) {
|
|
1054
|
+
try {
|
|
1055
|
+
if (typeof pattern !== "string" || utf8Length(pattern) > exports.RULE_EXPR_LIMITS.regexBytes)
|
|
1056
|
+
return null;
|
|
1057
|
+
const ast = new RegexParser(codePoints(pattern, Infinity)).parse();
|
|
1058
|
+
if (atomsOf(ast) > exports.RULE_EXPR_LIMITS.regexAtoms)
|
|
1059
|
+
return null;
|
|
1060
|
+
const prog = [];
|
|
1061
|
+
emit(ast, prog);
|
|
1062
|
+
prog.push({ op: "match" });
|
|
1063
|
+
return prog;
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
/** Whether a pattern is in the safe subset. */
|
|
1070
|
+
function isSafeRegex(pattern) {
|
|
1071
|
+
return compileRegex(pattern) !== null;
|
|
1072
|
+
}
|
|
1073
|
+
function classHas(r, c) {
|
|
1074
|
+
for (let k = 0; k < r.length; k += 2)
|
|
1075
|
+
if (c >= r[k] && c <= r[k + 1])
|
|
1076
|
+
return true;
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Pike VM: every thread advances in lock step, one code point at a time, so
|
|
1081
|
+
* the work is at most (subject length + 1) x (program size). True when some
|
|
1082
|
+
* substring of the subject (respecting `^` and `$`) is in the pattern's language.
|
|
1083
|
+
*/
|
|
1084
|
+
function runRegex(prog, subject, budget) {
|
|
1085
|
+
const size = prog.length;
|
|
1086
|
+
const mark = new Int32Array(size).fill(-1);
|
|
1087
|
+
let gen = 0;
|
|
1088
|
+
let clist = [];
|
|
1089
|
+
let nlist = [];
|
|
1090
|
+
const stack = [];
|
|
1091
|
+
const len = subject.length;
|
|
1092
|
+
const add = (list, pc0, pos) => {
|
|
1093
|
+
stack.length = 0;
|
|
1094
|
+
stack.push(pc0);
|
|
1095
|
+
while (stack.length) {
|
|
1096
|
+
const pc = stack.pop();
|
|
1097
|
+
if (mark[pc] === gen)
|
|
1098
|
+
continue;
|
|
1099
|
+
mark[pc] = gen;
|
|
1100
|
+
if (--budget.steps < 0)
|
|
1101
|
+
throw new BudgetExhausted();
|
|
1102
|
+
const inst = prog[pc];
|
|
1103
|
+
switch (inst.op) {
|
|
1104
|
+
case "jmp":
|
|
1105
|
+
stack.push(inst.x);
|
|
1106
|
+
break;
|
|
1107
|
+
case "split":
|
|
1108
|
+
stack.push(inst.y, inst.x);
|
|
1109
|
+
break;
|
|
1110
|
+
case "bol":
|
|
1111
|
+
if (pos === 0)
|
|
1112
|
+
stack.push(pc + 1);
|
|
1113
|
+
break;
|
|
1114
|
+
case "eol":
|
|
1115
|
+
if (pos === len)
|
|
1116
|
+
stack.push(pc + 1);
|
|
1117
|
+
break;
|
|
1118
|
+
case "match":
|
|
1119
|
+
return true;
|
|
1120
|
+
default:
|
|
1121
|
+
list.push(pc);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return false;
|
|
1125
|
+
};
|
|
1126
|
+
for (let pos = 0;; pos++) {
|
|
1127
|
+
// Threads already in clist were marked with this generation when added.
|
|
1128
|
+
if (add(clist, 0, pos))
|
|
1129
|
+
return true;
|
|
1130
|
+
if (pos === len)
|
|
1131
|
+
return false;
|
|
1132
|
+
const c = subject[pos];
|
|
1133
|
+
gen++;
|
|
1134
|
+
nlist = [];
|
|
1135
|
+
for (const pc of clist) {
|
|
1136
|
+
const inst = prog[pc];
|
|
1137
|
+
const ok = inst.op === "any" || (inst.op === "char" && inst.c === c) || (inst.op === "class" && classHas(inst.r, c) !== inst.neg);
|
|
1138
|
+
if (ok && add(nlist, pc + 1, pos + 1))
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
clist = nlist;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
// ---- CIDR -------------------------------------------------------------------
|
|
1145
|
+
const V4_PART = /^(0|[1-9][0-9]{0,2})$/;
|
|
1146
|
+
function parseV4(s) {
|
|
1147
|
+
const parts = s.split(".");
|
|
1148
|
+
if (parts.length !== 4)
|
|
1149
|
+
return null;
|
|
1150
|
+
const out = [];
|
|
1151
|
+
for (const p of parts) {
|
|
1152
|
+
if (!V4_PART.test(p))
|
|
1153
|
+
return null;
|
|
1154
|
+
const n = parseInt(p, 10);
|
|
1155
|
+
if (n > 255)
|
|
1156
|
+
return null;
|
|
1157
|
+
out.push(n);
|
|
1158
|
+
}
|
|
1159
|
+
return out;
|
|
1160
|
+
}
|
|
1161
|
+
/** An IPv4 or IPv6 address as 16 bytes (IPv4 as `::ffff:a.b.c.d`); null when not an address. */
|
|
1162
|
+
function parseIpBytes(s) {
|
|
1163
|
+
if (typeof s !== "string" || s.length > 64)
|
|
1164
|
+
return null;
|
|
1165
|
+
const v4 = parseV4(s);
|
|
1166
|
+
if (v4)
|
|
1167
|
+
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, ...v4];
|
|
1168
|
+
if (!s.includes(":"))
|
|
1169
|
+
return null;
|
|
1170
|
+
const halves = s.split("::");
|
|
1171
|
+
if (halves.length > 2)
|
|
1172
|
+
return null;
|
|
1173
|
+
const groups = (h) => {
|
|
1174
|
+
if (h === "")
|
|
1175
|
+
return [];
|
|
1176
|
+
const out = [];
|
|
1177
|
+
const parts = h.split(":");
|
|
1178
|
+
for (let k = 0; k < parts.length; k++) {
|
|
1179
|
+
const p = parts[k];
|
|
1180
|
+
if (k === parts.length - 1 && p.includes(".")) {
|
|
1181
|
+
const t = parseV4(p);
|
|
1182
|
+
if (!t)
|
|
1183
|
+
return null;
|
|
1184
|
+
out.push((t[0] << 8) | t[1], (t[2] << 8) | t[3]);
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
if (!/^[0-9A-Fa-f]{1,4}$/.test(p))
|
|
1188
|
+
return null;
|
|
1189
|
+
out.push(parseInt(p, 16));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
};
|
|
1194
|
+
const head = groups(halves[0]);
|
|
1195
|
+
const tail = halves.length === 2 ? groups(halves[1]) : [];
|
|
1196
|
+
if (!head || !tail)
|
|
1197
|
+
return null;
|
|
1198
|
+
if (halves.length === 2 && halves[0] !== "" && halves[0].includes("."))
|
|
1199
|
+
return null;
|
|
1200
|
+
let words;
|
|
1201
|
+
if (halves.length === 2) {
|
|
1202
|
+
if (head.length + tail.length > 7)
|
|
1203
|
+
return null;
|
|
1204
|
+
words = [...head, ...new Array(8 - head.length - tail.length).fill(0), ...tail];
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
if (head.length !== 8)
|
|
1208
|
+
return null;
|
|
1209
|
+
words = head;
|
|
1210
|
+
}
|
|
1211
|
+
const bytes = [];
|
|
1212
|
+
for (const w of words)
|
|
1213
|
+
bytes.push(w >> 8, w & 0xff);
|
|
1214
|
+
return bytes;
|
|
1215
|
+
}
|
|
1216
|
+
/** `a.b.c.d/n`, `x:y::z/n`, or a bare address (all bits). Null when malformed. */
|
|
1217
|
+
function parseCidr(s) {
|
|
1218
|
+
if (typeof s !== "string")
|
|
1219
|
+
return null;
|
|
1220
|
+
const slash = s.indexOf("/");
|
|
1221
|
+
const addr = slash === -1 ? s : s.slice(0, slash);
|
|
1222
|
+
if (addr.includes("%"))
|
|
1223
|
+
return null;
|
|
1224
|
+
const bytes = parseIpBytes(addr);
|
|
1225
|
+
if (!bytes)
|
|
1226
|
+
return null;
|
|
1227
|
+
const v4 = parseV4(addr) !== null;
|
|
1228
|
+
let prefix = 128;
|
|
1229
|
+
if (slash !== -1) {
|
|
1230
|
+
const p = s.slice(slash + 1);
|
|
1231
|
+
if (!/^(0|[1-9][0-9]{0,2})$/.test(p))
|
|
1232
|
+
return null;
|
|
1233
|
+
const n = parseInt(p, 10);
|
|
1234
|
+
if (n > (v4 ? 32 : 128))
|
|
1235
|
+
return null;
|
|
1236
|
+
prefix = v4 ? n + 96 : n;
|
|
1237
|
+
}
|
|
1238
|
+
return { bytes, prefix };
|
|
1239
|
+
}
|
|
1240
|
+
function inCidr(ip, net) {
|
|
1241
|
+
const pct = ip.indexOf("%");
|
|
1242
|
+
const bytes = parseIpBytes(pct === -1 ? ip : ip.slice(0, pct));
|
|
1243
|
+
if (!bytes)
|
|
1244
|
+
return false;
|
|
1245
|
+
let bits = net.prefix;
|
|
1246
|
+
for (let k = 0; k < 16 && bits > 0; k++, bits -= 8) {
|
|
1247
|
+
const mask = bits >= 8 ? 0xff : (0xff << (8 - bits)) & 0xff;
|
|
1248
|
+
if ((bytes[k] & mask) !== (net.bytes[k] & mask))
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
return true;
|
|
1252
|
+
}
|
|
1253
|
+
//# sourceMappingURL=rule-expr.js.map
|