agent-learning-kit 0.1.0__py3-none-any.whl
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.
- agent_learning_kit-0.1.0.dist-info/METADATA +381 -0
- agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
- agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
- agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
- agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
- fi/__init__.py +5 -0
- fi/alk/__init__.py +57 -0
- fi/alk/_facade.py +31 -0
- fi/alk/_module_alias.py +68 -0
- fi/alk/_paths.py +14 -0
- fi/alk/_schema.py +522 -0
- fi/alk/actions.py +727 -0
- fi/alk/bench/__init__.py +517 -0
- fi/alk/bench/_codeexec.py +213 -0
- fi/alk/bench/_coding.py +215 -0
- fi/alk/bench/_docker.py +237 -0
- fi/alk/bench/_grader.py +286 -0
- fi/alk/bench/_pull.py +212 -0
- fi/alk/bench/_voice.py +147 -0
- fi/alk/capabilities.py +627 -0
- fi/alk/cli.py +6396 -0
- fi/alk/config.py +130 -0
- fi/alk/cua_loop.py +562 -0
- fi/alk/evals.py +2351 -0
- fi/alk/extensions.py +163 -0
- fi/alk/harness/ARCHITECTURE.md +231 -0
- fi/alk/harness/DESIGN.md +246 -0
- fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
- fi/alk/harness/HOW-IT-WORKS.md +297 -0
- fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
- fi/alk/harness/README.md +417 -0
- fi/alk/harness/__init__.py +77 -0
- fi/alk/harness/__main__.py +3 -0
- fi/alk/harness/amend.py +312 -0
- fi/alk/harness/artifacts.py +319 -0
- fi/alk/harness/authoring_entrypoint.py +189 -0
- fi/alk/harness/authoring_runtime_validation.py +267 -0
- fi/alk/harness/backends/README.md +43 -0
- fi/alk/harness/backends/__init__.py +122 -0
- fi/alk/harness/backends/base.py +241 -0
- fi/alk/harness/backends/claude.py +211 -0
- fi/alk/harness/backends/files.py +182 -0
- fi/alk/harness/backends/vertex_gemini.py +457 -0
- fi/alk/harness/background_noise.py +95 -0
- fi/alk/harness/build.py +385 -0
- fi/alk/harness/bundle.py +593 -0
- fi/alk/harness/bundle_author_v2.py +1831 -0
- fi/alk/harness/bundle_v2.py +719 -0
- fi/alk/harness/call_runner.py +1440 -0
- fi/alk/harness/callback_http_adapter.py +111 -0
- fi/alk/harness/catalogue.py +287 -0
- fi/alk/harness/chat.py +428 -0
- fi/alk/harness/chat_call_runner.py +506 -0
- fi/alk/harness/checks.py +136 -0
- fi/alk/harness/cli.py +1354 -0
- fi/alk/harness/config.py +338 -0
- fi/alk/harness/contract.py +718 -0
- fi/alk/harness/credentials.py +674 -0
- fi/alk/harness/data/persona_vocabulary.json +111 -0
- fi/alk/harness/environment.py +99 -0
- fi/alk/harness/environment_plan.py +168 -0
- fi/alk/harness/events.py +125 -0
- fi/alk/harness/executor.py +304 -0
- fi/alk/harness/folder.py +234 -0
- fi/alk/harness/generated_runtime.py +815 -0
- fi/alk/harness/github.py +72 -0
- fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
- fi/alk/harness/hosted_entrypoint.py +2402 -0
- fi/alk/harness/hosted_scheduler.py +2218 -0
- fi/alk/harness/job.py +426 -0
- fi/alk/harness/judge.py +184 -0
- fi/alk/harness/livekit_source.py +50 -0
- fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
- fi/alk/harness/observability.py +208 -0
- fi/alk/harness/outbound.py +3252 -0
- fi/alk/harness/packaging.py +515 -0
- fi/alk/harness/persona_guides.py +157 -0
- fi/alk/harness/platform.py +692 -0
- fi/alk/harness/process_preflight.py +764 -0
- fi/alk/harness/process_runtime.py +5670 -0
- fi/alk/harness/prove.py +425 -0
- fi/alk/harness/provider_import.py +703 -0
- fi/alk/harness/provider_lifecycle.py +392 -0
- fi/alk/harness/provision.py +2896 -0
- fi/alk/harness/reception.py +147 -0
- fi/alk/harness/retell_chat_call_runner.py +373 -0
- fi/alk/harness/run/__init__.py +296 -0
- fi/alk/harness/run/alk.py +184 -0
- fi/alk/harness/run/call.py +162 -0
- fi/alk/harness/run/conversation.py +264 -0
- fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
- fi/alk/harness/run/evidence.py +195 -0
- fi/alk/harness/run/grade.py +598 -0
- fi/alk/harness/run/live.py +297 -0
- fi/alk/harness/run/models.py +56 -0
- fi/alk/harness/run/platform_evals.py +227 -0
- fi/alk/harness/run/sdk_voice.py +130 -0
- fi/alk/harness/run/simulation.py +1209 -0
- fi/alk/harness/run/stage.py +91 -0
- fi/alk/harness/run/targets.py +508 -0
- fi/alk/harness/run/tools.py +601 -0
- fi/alk/harness/run/voice.py +340 -0
- fi/alk/harness/runtime.py +172 -0
- fi/alk/harness/sandbox_server.py +2011 -0
- fi/alk/harness/sandbox_worker.py +44 -0
- fi/alk/harness/scenario.py +1048 -0
- fi/alk/harness/scenario_source.py +879 -0
- fi/alk/harness/scenario_tools.py +1143 -0
- fi/alk/harness/scenarios.py +915 -0
- fi/alk/harness/secrets.py +168 -0
- fi/alk/harness/service_catalog.py +97 -0
- fi/alk/harness/session.py +391 -0
- fi/alk/harness/sessions.py +372 -0
- fi/alk/harness/simulator.py +76 -0
- fi/alk/harness/simulator_voice.py +928 -0
- fi/alk/harness/skills/build-environment/SKILL.md +538 -0
- fi/alk/harness/skills/harness.md +131 -0
- fi/alk/harness/skills/kinds/chat.md +48 -0
- fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
- fi/alk/harness/skills/kinds/voice.md +59 -0
- fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
- fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
- fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
- fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
- fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
- fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
- fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
- fi/alk/harness/source_data_invariants.py +444 -0
- fi/alk/harness/source_tool_evidence.py +79 -0
- fi/alk/harness/sources.py +253 -0
- fi/alk/harness/spend.py +140 -0
- fi/alk/harness/tool_trace_proxy.py +104 -0
- fi/alk/harness/tools.py +1018 -0
- fi/alk/harness/understand.py +169 -0
- fi/alk/harness/voicemail_audio.py +74 -0
- fi/alk/harness/world/__init__.py +33 -0
- fi/alk/harness/world/errors.py +68 -0
- fi/alk/harness/world/expectations.py +91 -0
- fi/alk/harness/world/handle.py +538 -0
- fi/alk/harness/world/kinds.py +196 -0
- fi/alk/harness/world/mutate.py +186 -0
- fi/alk/harness/world/probe.py +413 -0
- fi/alk/harness/world/provision.py +511 -0
- fi/alk/harness/world/provisioned.py +191 -0
- fi/alk/harness/world/runtime.py +616 -0
- fi/alk/harness/world/snapshot.py +288 -0
- fi/alk/harness/world/stores/__init__.py +305 -0
- fi/alk/harness/world/stores/container.py +215 -0
- fi/alk/harness/world/stores/inprocess.py +346 -0
- fi/alk/harness/world/stores/postgres.py +481 -0
- fi/alk/harness/world/stores/prove.py +202 -0
- fi/alk/harness/world/stores/sqlite.py +245 -0
- fi/alk/harness/world/stores/written.py +182 -0
- fi/alk/harness/world/tools.py +1516 -0
- fi/alk/harness/world/workspace.py +144 -0
- fi/alk/image_loop.py +453 -0
- fi/alk/image_perturb.py +241 -0
- fi/alk/improve.py +274 -0
- fi/alk/live/__init__.py +154 -0
- fi/alk/live/_attribution.py +184 -0
- fi/alk/live/_capture.py +264 -0
- fi/alk/live/_codec.py +391 -0
- fi/alk/live/_contract.py +134 -0
- fi/alk/live/_loopback.py +316 -0
- fi/alk/live/_perturb.py +449 -0
- fi/alk/live/_runner.py +386 -0
- fi/alk/live/_stats.py +561 -0
- fi/alk/live/_transcript.py +240 -0
- fi/alk/live/_workers/__init__.py +9 -0
- fi/alk/live/_workers/a2a_worker.py +316 -0
- fi/alk/live/_workers/langgraph_worker.py +217 -0
- fi/alk/live/_workers/livekit_worker.py +207 -0
- fi/alk/live/_workers/mcp_loopback_server.py +46 -0
- fi/alk/live/_workers/mcp_worker.py +158 -0
- fi/alk/live/_workers/pipecat_worker.py +189 -0
- fi/alk/live/a2a_lane.py +138 -0
- fi/alk/live/langgraph_lane.py +339 -0
- fi/alk/live/livekit_lane.py +376 -0
- fi/alk/live/mcp_lane.py +172 -0
- fi/alk/live/pipecat_lane.py +341 -0
- fi/alk/live/voice_redteam.py +494 -0
- fi/alk/loss.py +306 -0
- fi/alk/optimize.py +36260 -0
- fi/alk/practice/__init__.py +51 -0
- fi/alk/practice/_assess.py +103 -0
- fi/alk/practice/_budget.py +81 -0
- fi/alk/practice/_calibrate.py +69 -0
- fi/alk/practice/_capstone.py +86 -0
- fi/alk/practice/_contract.py +91 -0
- fi/alk/practice/_diagnose.py +79 -0
- fi/alk/practice/_drill.py +196 -0
- fi/alk/practice/_experiment.py +720 -0
- fi/alk/practice/_schedule.py +102 -0
- fi/alk/practice/_store.py +194 -0
- fi/alk/practice/_trainer.py +245 -0
- fi/alk/practice/_update.py +125 -0
- fi/alk/redteam.py +2621 -0
- fi/alk/rewardhack.py +237 -0
- fi/alk/simulate.py +10351 -0
- fi/alk/studio/__init__.py +82 -0
- fi/alk/studio/_bias.py +314 -0
- fi/alk/studio/_calibration.py +522 -0
- fi/alk/studio/_coverage.py +262 -0
- fi/alk/studio/_download.py +665 -0
- fi/alk/studio/_fidelity_attack.py +114 -0
- fi/alk/studio/_generate.py +652 -0
- fi/alk/studio/_library.py +370 -0
- fi/alk/studio/_scan.py +134 -0
- fi/alk/studio/_upgrade.py +42 -0
- fi/alk/studio/_vendor.py +172 -0
- fi/alk/suite.py +4200 -0
- fi/alk/tasks.py +828 -0
- fi/alk/telemetry/__init__.py +149 -0
- fi/alk/telemetry/_contract.py +141 -0
- fi/alk/telemetry/_emit.py +182 -0
- fi/alk/telemetry/_ledger.py +296 -0
- fi/alk/telemetry/_queue.py +127 -0
- fi/alk/telemetry/_row.py +294 -0
- fi/alk/telemetry/_run.py +233 -0
- fi/alk/telemetry/_sync.py +193 -0
- fi/alk/telemetry/_url.py +119 -0
- fi/alk/trinity.py +49397 -0
- fi/alk/voice_loop.py +174 -0
- fi/api/__init__.py +1 -0
- fi/api/auth.py +137 -0
- fi/api/types.py +29 -0
- fi/cli/__init__.py +9 -0
- fi/cli/assertions/__init__.py +25 -0
- fi/cli/assertions/conditions.py +76 -0
- fi/cli/assertions/evaluator.py +286 -0
- fi/cli/assertions/exit_codes.py +20 -0
- fi/cli/assertions/parser.py +131 -0
- fi/cli/assertions/reporter.py +194 -0
- fi/cli/commands/__init__.py +9 -0
- fi/cli/commands/config.py +165 -0
- fi/cli/commands/export.py +208 -0
- fi/cli/commands/init.py +112 -0
- fi/cli/commands/list_cmd.py +213 -0
- fi/cli/commands/run.py +486 -0
- fi/cli/commands/validate.py +173 -0
- fi/cli/commands/view.py +424 -0
- fi/cli/config/__init__.py +6 -0
- fi/cli/config/defaults.py +206 -0
- fi/cli/config/loader.py +155 -0
- fi/cli/config/schema.py +174 -0
- fi/cli/main.py +78 -0
- fi/cli/output/__init__.py +6 -0
- fi/cli/output/formatters.py +106 -0
- fi/cli/output/reporters.py +46 -0
- fi/cli/storage/__init__.py +5 -0
- fi/cli/storage/run_history.py +249 -0
- fi/cli/utils/__init__.py +5 -0
- fi/cli/utils/console.py +44 -0
- fi/evals/__init__.py +131 -0
- fi/evals/autoeval/__init__.py +137 -0
- fi/evals/autoeval/analyzer.py +211 -0
- fi/evals/autoeval/config.py +244 -0
- fi/evals/autoeval/export.py +213 -0
- fi/evals/autoeval/interactive.py +283 -0
- fi/evals/autoeval/pipeline.py +625 -0
- fi/evals/autoeval/prompts.py +139 -0
- fi/evals/autoeval/recommender.py +242 -0
- fi/evals/autoeval/rules.py +589 -0
- fi/evals/autoeval/templates.py +299 -0
- fi/evals/autoeval/types.py +232 -0
- fi/evals/core/__init__.py +16 -0
- fi/evals/core/cloud_registry.py +184 -0
- fi/evals/core/engines.py +368 -0
- fi/evals/core/evaluate.py +319 -0
- fi/evals/core/judge_prompt.py +90 -0
- fi/evals/core/prompt_generator.py +83 -0
- fi/evals/core/registry.py +57 -0
- fi/evals/core/result.py +55 -0
- fi/evals/evaluator.py +721 -0
- fi/evals/execution.py +168 -0
- fi/evals/feedback/__init__.py +32 -0
- fi/evals/feedback/calibrator.py +160 -0
- fi/evals/feedback/collector.py +214 -0
- fi/evals/feedback/hooks.py +81 -0
- fi/evals/feedback/retriever.py +128 -0
- fi/evals/feedback/store.py +272 -0
- fi/evals/feedback/types.py +99 -0
- fi/evals/framework/README.md +79 -0
- fi/evals/framework/__init__.py +267 -0
- fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
- fi/evals/framework/backends/__init__.py +99 -0
- fi/evals/framework/backends/_container.py +141 -0
- fi/evals/framework/backends/_utils.py +145 -0
- fi/evals/framework/backends/base.py +223 -0
- fi/evals/framework/backends/celery_backend.py +417 -0
- fi/evals/framework/backends/celery_worker.py +78 -0
- fi/evals/framework/backends/kubernetes_backend.py +665 -0
- fi/evals/framework/backends/ray_backend.py +521 -0
- fi/evals/framework/backends/temporal.py +350 -0
- fi/evals/framework/backends/temporal_worker.py +126 -0
- fi/evals/framework/backends/thread_pool.py +286 -0
- fi/evals/framework/context.py +258 -0
- fi/evals/framework/enrichment.py +306 -0
- fi/evals/framework/evals/__init__.py +68 -0
- fi/evals/framework/evals/agentic.py +399 -0
- fi/evals/framework/evals/builder.py +609 -0
- fi/evals/framework/evals/semantic.py +142 -0
- fi/evals/framework/evaluator.py +647 -0
- fi/evals/framework/evaluators/__init__.py +22 -0
- fi/evals/framework/evaluators/blocking.py +347 -0
- fi/evals/framework/evaluators/non_blocking.py +577 -0
- fi/evals/framework/propagation.py +421 -0
- fi/evals/framework/protocols.py +385 -0
- fi/evals/framework/registry.py +370 -0
- fi/evals/framework/resilience/__init__.py +150 -0
- fi/evals/framework/resilience/circuit_breaker.py +309 -0
- fi/evals/framework/resilience/degradation.py +355 -0
- fi/evals/framework/resilience/health.py +505 -0
- fi/evals/framework/resilience/rate_limiter.py +228 -0
- fi/evals/framework/resilience/retry.py +274 -0
- fi/evals/framework/resilience/types.py +288 -0
- fi/evals/framework/resilience/wrapper.py +433 -0
- fi/evals/framework/types.py +218 -0
- fi/evals/guardrails/README.md +915 -0
- fi/evals/guardrails/__init__.py +96 -0
- fi/evals/guardrails/backends/__init__.py +43 -0
- fi/evals/guardrails/backends/azure.py +361 -0
- fi/evals/guardrails/backends/base.py +88 -0
- fi/evals/guardrails/backends/generic_llm.py +163 -0
- fi/evals/guardrails/backends/granite.py +216 -0
- fi/evals/guardrails/backends/llamaguard.py +221 -0
- fi/evals/guardrails/backends/local_base.py +479 -0
- fi/evals/guardrails/backends/openai.py +365 -0
- fi/evals/guardrails/backends/qwen.py +170 -0
- fi/evals/guardrails/backends/shieldgemma.py +154 -0
- fi/evals/guardrails/backends/turing.py +235 -0
- fi/evals/guardrails/backends/vllm_client.py +321 -0
- fi/evals/guardrails/backends/wildguard.py +188 -0
- fi/evals/guardrails/base.py +888 -0
- fi/evals/guardrails/config.py +221 -0
- fi/evals/guardrails/discovery.py +243 -0
- fi/evals/guardrails/gateway.py +437 -0
- fi/evals/guardrails/registry.py +231 -0
- fi/evals/guardrails/scanners/__init__.py +127 -0
- fi/evals/guardrails/scanners/base.py +191 -0
- fi/evals/guardrails/scanners/code_injection.py +243 -0
- fi/evals/guardrails/scanners/eval_delegate.py +574 -0
- fi/evals/guardrails/scanners/invisible_chars.py +351 -0
- fi/evals/guardrails/scanners/jailbreak.py +412 -0
- fi/evals/guardrails/scanners/language.py +288 -0
- fi/evals/guardrails/scanners/pipeline.py +260 -0
- fi/evals/guardrails/scanners/regex.py +311 -0
- fi/evals/guardrails/scanners/secrets.py +274 -0
- fi/evals/guardrails/scanners/topics.py +649 -0
- fi/evals/guardrails/scanners/urls.py +341 -0
- fi/evals/guardrails/types.py +96 -0
- fi/evals/llm/__init__.py +3 -0
- fi/evals/llm/base_llm_provider.py +35 -0
- fi/evals/llm/providers/litellm.py +70 -0
- fi/evals/local/__init__.py +90 -0
- fi/evals/local/evaluator.py +690 -0
- fi/evals/local/execution_mode.py +121 -0
- fi/evals/local/llm.py +489 -0
- fi/evals/local/metrics/__init__.py +19 -0
- fi/evals/local/registry.py +360 -0
- fi/evals/manager.py +1018 -0
- fi/evals/manager_types.py +362 -0
- fi/evals/metrics/__init__.py +185 -0
- fi/evals/metrics/agents/__init__.py +74 -0
- fi/evals/metrics/agents/metrics.py +693 -0
- fi/evals/metrics/agents/report.py +36463 -0
- fi/evals/metrics/agents/types.py +160 -0
- fi/evals/metrics/base_llm_metric.py +111 -0
- fi/evals/metrics/base_metric.py +138 -0
- fi/evals/metrics/code_security/__init__.py +305 -0
- fi/evals/metrics/code_security/analyzer.py +985 -0
- fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
- fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
- fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
- fi/evals/metrics/code_security/benchmarks/types.py +308 -0
- fi/evals/metrics/code_security/detectors/__init__.py +186 -0
- fi/evals/metrics/code_security/detectors/base.py +394 -0
- fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
- fi/evals/metrics/code_security/detectors/injection.py +744 -0
- fi/evals/metrics/code_security/detectors/secrets.py +287 -0
- fi/evals/metrics/code_security/detectors/serialization.py +192 -0
- fi/evals/metrics/code_security/joint_metrics.py +588 -0
- fi/evals/metrics/code_security/judges/__init__.py +83 -0
- fi/evals/metrics/code_security/judges/base.py +238 -0
- fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
- fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
- fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
- fi/evals/metrics/code_security/metrics.py +388 -0
- fi/evals/metrics/code_security/modes/__init__.py +63 -0
- fi/evals/metrics/code_security/modes/adversarial.py +284 -0
- fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
- fi/evals/metrics/code_security/modes/base.py +283 -0
- fi/evals/metrics/code_security/modes/instruct.py +253 -0
- fi/evals/metrics/code_security/modes/repair.py +230 -0
- fi/evals/metrics/code_security/reports/__init__.py +57 -0
- fi/evals/metrics/code_security/reports/generator.py +404 -0
- fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
- fi/evals/metrics/code_security/types.py +534 -0
- fi/evals/metrics/function_calling/__init__.py +34 -0
- fi/evals/metrics/function_calling/metrics.py +573 -0
- fi/evals/metrics/function_calling/types.py +87 -0
- fi/evals/metrics/hallucination/__init__.py +54 -0
- fi/evals/metrics/hallucination/detector.py +149 -0
- fi/evals/metrics/hallucination/metrics.py +390 -0
- fi/evals/metrics/hallucination/nli.py +253 -0
- fi/evals/metrics/hallucination/sentinel.py +106 -0
- fi/evals/metrics/hallucination/types.py +132 -0
- fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
- fi/evals/metrics/heuristics/json_metrics.py +87 -0
- fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
- fi/evals/metrics/heuristics/string_metrics.py +391 -0
- fi/evals/metrics/llm_as_judges/__init__.py +17 -0
- fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
- fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
- fi/evals/metrics/llm_as_judges/types.py +48 -0
- fi/evals/metrics/rag/__init__.py +111 -0
- fi/evals/metrics/rag/advanced/__init__.py +14 -0
- fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
- fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
- fi/evals/metrics/rag/generation/__init__.py +17 -0
- fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
- fi/evals/metrics/rag/generation/context_utilization.py +245 -0
- fi/evals/metrics/rag/generation/faithfulness.py +241 -0
- fi/evals/metrics/rag/generation/groundedness.py +131 -0
- fi/evals/metrics/rag/rag_score.py +277 -0
- fi/evals/metrics/rag/retrieval/__init__.py +20 -0
- fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
- fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
- fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
- fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
- fi/evals/metrics/rag/retrieval/ranking.py +261 -0
- fi/evals/metrics/rag/types.py +100 -0
- fi/evals/metrics/rag/utils/__init__.py +62 -0
- fi/evals/metrics/rag/utils/claims.py +189 -0
- fi/evals/metrics/rag/utils/entities.py +244 -0
- fi/evals/metrics/rag/utils/nli.py +92 -0
- fi/evals/metrics/rag/utils/similarity.py +345 -0
- fi/evals/metrics/structured/__init__.py +114 -0
- fi/evals/metrics/structured/field_completeness.py +313 -0
- fi/evals/metrics/structured/hierarchy_score.py +366 -0
- fi/evals/metrics/structured/json_validation.py +190 -0
- fi/evals/metrics/structured/schema_compliance.py +280 -0
- fi/evals/metrics/structured/structured_output_score.py +298 -0
- fi/evals/metrics/structured/types.py +108 -0
- fi/evals/metrics/structured/validators/__init__.py +30 -0
- fi/evals/metrics/structured/validators/base.py +189 -0
- fi/evals/metrics/structured/validators/json_validator.py +196 -0
- fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
- fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
- fi/evals/otel/__init__.py +266 -0
- fi/evals/otel/config.py +400 -0
- fi/evals/otel/conventions.py +463 -0
- fi/evals/otel/enrichment.py +371 -0
- fi/evals/otel/instrumentors/__init__.py +140 -0
- fi/evals/otel/instrumentors/anthropic.py +517 -0
- fi/evals/otel/instrumentors/base.py +382 -0
- fi/evals/otel/instrumentors/openai.py +673 -0
- fi/evals/otel/processors/__init__.py +36 -0
- fi/evals/otel/processors/base.py +473 -0
- fi/evals/otel/processors/cost.py +445 -0
- fi/evals/otel/processors/evaluation.py +559 -0
- fi/evals/otel/processors/llm.py +462 -0
- fi/evals/otel/tracer.py +506 -0
- fi/evals/otel/types.py +232 -0
- fi/evals/otel_utils.py +23 -0
- fi/evals/protect.py +671 -0
- fi/evals/protect_input_adapter.py +154 -0
- fi/evals/streaming/__init__.py +88 -0
- fi/evals/streaming/buffer.py +213 -0
- fi/evals/streaming/evaluator.py +551 -0
- fi/evals/streaming/policy.py +307 -0
- fi/evals/streaming/scorers.py +368 -0
- fi/evals/streaming/types.py +238 -0
- fi/evals/templates.py +472 -0
- fi/evals/types.py +156 -0
- fi/opt/__init__.py +221 -0
- fi/opt/_objective_scoring.py +85 -0
- fi/opt/base/__init__.py +11 -0
- fi/opt/base/base_generator.py +33 -0
- fi/opt/base/base_mapper.py +26 -0
- fi/opt/base/base_optimizer.py +45 -0
- fi/opt/base/evaluator.py +211 -0
- fi/opt/components.py +3095 -0
- fi/opt/datamappers/__init__.py +3 -0
- fi/opt/datamappers/basic_mapper.py +40 -0
- fi/opt/deployment.py +1021 -0
- fi/opt/evidence.py +4332 -0
- fi/opt/generators/__init__.py +3 -0
- fi/opt/generators/litellm.py +66 -0
- fi/opt/integrations/__init__.py +23 -0
- fi/opt/integrations/generative_suite.py +410 -0
- fi/opt/integrations/simulate.py +1313 -0
- fi/opt/mutations.py +771 -0
- fi/opt/observability.py +4639 -0
- fi/opt/optimizer_trace.py +889 -0
- fi/opt/optimizers/__init__.py +80 -0
- fi/opt/optimizers/agent.py +331 -0
- fi/opt/optimizers/agent_bandit.py +392 -0
- fi/opt/optimizers/agent_curriculum.py +635 -0
- fi/opt/optimizers/agent_evolution.py +894 -0
- fi/opt/optimizers/agent_feedback.py +1863 -0
- fi/opt/optimizers/agent_pareto.py +547 -0
- fi/opt/optimizers/agent_social_memory.py +1113 -0
- fi/opt/optimizers/agent_tpe.py +321 -0
- fi/opt/optimizers/bayesian_search.py +449 -0
- fi/opt/optimizers/council.py +2075 -0
- fi/opt/optimizers/futureagi_replay.py +799 -0
- fi/opt/optimizers/gepa.py +322 -0
- fi/opt/optimizers/metaprompt.py +243 -0
- fi/opt/optimizers/promptwizard.py +417 -0
- fi/opt/optimizers/protegi.py +329 -0
- fi/opt/optimizers/random_search.py +224 -0
- fi/opt/research.py +518 -0
- fi/opt/simulation.py +260 -0
- fi/opt/targets.py +232 -0
- fi/opt/types.py +66 -0
- fi/opt/utils/__init__.py +4 -0
- fi/opt/utils/early_stopping.py +266 -0
- fi/opt/utils/setup_logging.py +82 -0
- fi/simulate/__init__.py +540 -0
- fi/simulate/_hashing.py +35 -0
- fi/simulate/_logging.py +10 -0
- fi/simulate/adapters.py +87 -0
- fi/simulate/agent/__init__.py +120 -0
- fi/simulate/agent/browser.py +658 -0
- fi/simulate/agent/definition.py +587 -0
- fi/simulate/agent/frameworks.py +3528 -0
- fi/simulate/agent/generic.py +8286 -0
- fi/simulate/agent/import_probe.py +227 -0
- fi/simulate/agent/memory.py +905 -0
- fi/simulate/agent/mocks.py +101 -0
- fi/simulate/agent/multi_agent.py +361 -0
- fi/simulate/agent/orchestration.py +903 -0
- fi/simulate/agent/realtime.py +665 -0
- fi/simulate/agent/wrapper.py +99 -0
- fi/simulate/agent/wrappers/__init__.py +18 -0
- fi/simulate/agent/wrappers/anthropic.py +62 -0
- fi/simulate/agent/wrappers/gemini.py +65 -0
- fi/simulate/agent/wrappers/http.py +404 -0
- fi/simulate/agent/wrappers/langchain.py +80 -0
- fi/simulate/agent/wrappers/openai.py +75 -0
- fi/simulate/agent/wrappers/websocket.py +326 -0
- fi/simulate/artifacts/__init__.py +11 -0
- fi/simulate/artifacts/manifest.py +62 -0
- fi/simulate/cli.py +20560 -0
- fi/simulate/endpoints/__init__.py +45 -0
- fi/simulate/endpoints/_http_actor.py +73 -0
- fi/simulate/endpoints/actor_sources.py +243 -0
- fi/simulate/endpoints/base.py +107 -0
- fi/simulate/endpoints/builtins.py +10 -0
- fi/simulate/endpoints/callable.py +95 -0
- fi/simulate/endpoints/http.py +76 -0
- fi/simulate/endpoints/livekit.py +138 -0
- fi/simulate/endpoints/originators.py +132 -0
- fi/simulate/endpoints/profiles.py +348 -0
- fi/simulate/endpoints/retell.py +633 -0
- fi/simulate/endpoints/vapi.py +205 -0
- fi/simulate/endpoints/websocket.py +76 -0
- fi/simulate/environment.py +33026 -0
- fi/simulate/environments/__init__.py +11 -0
- fi/simulate/environments/base.py +73 -0
- fi/simulate/environments/chat.py +697 -0
- fi/simulate/environments/voice.py +212 -0
- fi/simulate/evaluation/__init__.py +4 -0
- fi/simulate/evaluation/ai_eval.py +227 -0
- fi/simulate/evidence/__init__.py +35 -0
- fi/simulate/evidence/base.py +59 -0
- fi/simulate/evidence/caller_observed.py +50 -0
- fi/simulate/evidence/livekit_instrumentation.py +51 -0
- fi/simulate/evidence/livekit_room.py +50 -0
- fi/simulate/evidence/otel.py +49 -0
- fi/simulate/evidence/providers/__init__.py +24 -0
- fi/simulate/evidence/providers/base.py +61 -0
- fi/simulate/evidence/providers/retell.py +376 -0
- fi/simulate/evidence/providers/vapi.py +426 -0
- fi/simulate/hosted/__init__.py +32 -0
- fi/simulate/hosted/child_entrypoint.py +306 -0
- fi/simulate/hosted/job.py +150 -0
- fi/simulate/hosted/targets.py +53 -0
- fi/simulate/instrumentation/__init__.py +5 -0
- fi/simulate/instrumentation/livekit/__init__.py +122 -0
- fi/simulate/manifest.py +1033 -0
- fi/simulate/matrix_cli.py +165 -0
- fi/simulate/realtime/__init__.py +40 -0
- fi/simulate/realtime/events.py +107 -0
- fi/simulate/realtime/media.py +61 -0
- fi/simulate/realtime/session.py +91 -0
- fi/simulate/recording/__init__.py +5 -0
- fi/simulate/recording/room_recorder.py +326 -0
- fi/simulate/registry.py +185 -0
- fi/simulate/results/__init__.py +9 -0
- fi/simulate/results/base.py +18 -0
- fi/simulate/results/filesystem.py +71 -0
- fi/simulate/results/futureagi.py +1340 -0
- fi/simulate/runtime/__init__.py +85 -0
- fi/simulate/runtime/capabilities.py +40 -0
- fi/simulate/runtime/events.py +63 -0
- fi/simulate/runtime/failures.py +25 -0
- fi/simulate/runtime/ids.py +34 -0
- fi/simulate/runtime/plan.py +70 -0
- fi/simulate/runtime/planner.py +102 -0
- fi/simulate/runtime/report.py +174 -0
- fi/simulate/runtime/run.py +75 -0
- fi/simulate/runtime/runner.py +333 -0
- fi/simulate/runtime/spec.py +186 -0
- fi/simulate/simulation/__init__.py +30 -0
- fi/simulate/simulation/behavior_policy.py +425 -0
- fi/simulate/simulation/bridge/__init__.py +9 -0
- fi/simulate/simulation/bridge/audio.py +29 -0
- fi/simulate/simulation/bridge/connector.py +46 -0
- fi/simulate/simulation/bridge/livekit.py +252 -0
- fi/simulate/simulation/bridge/retell.py +188 -0
- fi/simulate/simulation/bridge/vapi.py +177 -0
- fi/simulate/simulation/contract.py +419 -0
- fi/simulate/simulation/engines/__init__.py +12 -0
- fi/simulate/simulation/engines/base.py +21 -0
- fi/simulate/simulation/engines/cloud.py +517 -0
- fi/simulate/simulation/engines/livekit.py +4167 -0
- fi/simulate/simulation/engines/local_text.py +89 -0
- fi/simulate/simulation/fidelity.py +374 -0
- fi/simulate/simulation/gemini_tts_stream.py +110 -0
- fi/simulate/simulation/generator.py +91 -0
- fi/simulate/simulation/goal_machine.py +185 -0
- fi/simulate/simulation/livekit_models.py +467 -0
- fi/simulate/simulation/matrix.py +170 -0
- fi/simulate/simulation/models.py +279 -0
- fi/simulate/simulation/runner.py +153 -0
- fi/simulate/simulation/synthetic.py +880 -0
- fi/simulate/simulation/voice_prompt.py +502 -0
- fi/simulate/simulator/__init__.py +55 -0
- fi/simulate/simulator/builtins.py +53 -0
- fi/simulate/suite.py +1288 -0
- fi/simulate/utils/routes.py +164 -0
- fi/simulate/voice.py +225 -0
- fi/simulate/voice_cli.py +182 -0
- fi/utils/__init__.py +1 -0
- fi/utils/constants.py +14 -0
- fi/utils/errors.py +200 -0
- fi/utils/executor.py +26 -0
- fi/utils/routes.py +119 -0
- fi/utils/utils.py +17 -0
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code Analyzer for Security Evaluation.
|
|
3
|
+
|
|
4
|
+
Provides language-aware code analysis including:
|
|
5
|
+
- AST parsing (Python native, regex-based for others)
|
|
6
|
+
- Function/class extraction
|
|
7
|
+
- String literal extraction
|
|
8
|
+
- Import analysis
|
|
9
|
+
- Language detection
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import ast
|
|
13
|
+
import re
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class FunctionInfo:
|
|
21
|
+
"""Information about a function definition."""
|
|
22
|
+
name: str
|
|
23
|
+
line: int
|
|
24
|
+
end_line: Optional[int]
|
|
25
|
+
args: List[str]
|
|
26
|
+
decorators: List[str]
|
|
27
|
+
is_async: bool
|
|
28
|
+
docstring: Optional[str]
|
|
29
|
+
calls: List[str] # Functions called within this function
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ImportInfo:
|
|
34
|
+
"""Information about an import statement."""
|
|
35
|
+
module: str
|
|
36
|
+
names: List[str] # Specific names imported (empty for "import module")
|
|
37
|
+
alias: Optional[str]
|
|
38
|
+
line: int
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class StringLiteral:
|
|
43
|
+
"""A string literal found in code."""
|
|
44
|
+
value: str
|
|
45
|
+
line: int
|
|
46
|
+
column: Optional[int]
|
|
47
|
+
is_fstring: bool
|
|
48
|
+
context: Optional[str] # e.g., "sql_query", "file_path", "command"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class AnalysisResult:
|
|
53
|
+
"""Result of code analysis."""
|
|
54
|
+
language: str
|
|
55
|
+
functions: List[FunctionInfo]
|
|
56
|
+
imports: List[ImportInfo]
|
|
57
|
+
strings: List[StringLiteral]
|
|
58
|
+
variables: Dict[str, List[int]] # Variable name -> lines where assigned
|
|
59
|
+
dangerous_calls: List[Tuple[str, int]] # (function_name, line)
|
|
60
|
+
parse_errors: List[str]
|
|
61
|
+
raw_ast: Optional[Any] # Language-specific AST
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class LanguageAnalyzer(ABC):
|
|
65
|
+
"""Base class for language-specific code analyzers."""
|
|
66
|
+
|
|
67
|
+
language: str = "unknown"
|
|
68
|
+
|
|
69
|
+
# Dangerous functions to track
|
|
70
|
+
DANGEROUS_FUNCTIONS: List[str] = []
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def parse(self, code: str) -> Optional[Any]:
|
|
74
|
+
"""Parse code into AST or structured representation."""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def analyze(self, code: str) -> AnalysisResult:
|
|
79
|
+
"""Perform full analysis of code."""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def _find_dangerous_calls(
|
|
83
|
+
self, code: str, functions: Optional[List[str]] = None
|
|
84
|
+
) -> List[Tuple[str, int]]:
|
|
85
|
+
"""Find calls to dangerous functions."""
|
|
86
|
+
dangerous = functions or self.DANGEROUS_FUNCTIONS
|
|
87
|
+
calls = []
|
|
88
|
+
|
|
89
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
90
|
+
for func in dangerous:
|
|
91
|
+
# Handle both module.function and function patterns
|
|
92
|
+
patterns = [
|
|
93
|
+
rf"\b{re.escape(func)}\s*\(",
|
|
94
|
+
rf"\b{func.split('.')[-1]}\s*\(" if "." in func else None,
|
|
95
|
+
]
|
|
96
|
+
for pattern in patterns:
|
|
97
|
+
if pattern and re.search(pattern, line):
|
|
98
|
+
calls.append((func, i))
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
return calls
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class PythonAnalyzer(LanguageAnalyzer):
|
|
105
|
+
"""Python-specific code analyzer using the ast module."""
|
|
106
|
+
|
|
107
|
+
language = "python"
|
|
108
|
+
|
|
109
|
+
DANGEROUS_FUNCTIONS = [
|
|
110
|
+
# Command execution
|
|
111
|
+
"os.system", "os.popen", "os.spawn", "os.spawnl", "os.spawnle",
|
|
112
|
+
"subprocess.call", "subprocess.run", "subprocess.Popen",
|
|
113
|
+
"subprocess.check_output", "subprocess.check_call",
|
|
114
|
+
"commands.getoutput", "commands.getstatusoutput",
|
|
115
|
+
# Code execution
|
|
116
|
+
"eval", "exec", "compile", "execfile",
|
|
117
|
+
# Deserialization
|
|
118
|
+
"pickle.loads", "pickle.load", "cPickle.loads", "cPickle.load",
|
|
119
|
+
"yaml.load", "yaml.unsafe_load",
|
|
120
|
+
"marshal.loads", "marshal.load",
|
|
121
|
+
# SQL
|
|
122
|
+
"cursor.execute", "connection.execute", "db.execute",
|
|
123
|
+
"engine.execute", "session.execute",
|
|
124
|
+
# File operations
|
|
125
|
+
"open", "file",
|
|
126
|
+
# Network
|
|
127
|
+
"urllib.urlopen", "urllib2.urlopen", "requests.get", "requests.post",
|
|
128
|
+
"httplib.HTTPConnection", "http.client.HTTPConnection",
|
|
129
|
+
# Crypto (weak)
|
|
130
|
+
"hashlib.md5", "hashlib.sha1",
|
|
131
|
+
"Crypto.Cipher.DES", "Crypto.Cipher.ARC4",
|
|
132
|
+
# Random (insecure)
|
|
133
|
+
"random.random", "random.randint", "random.choice",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
def parse(self, code: str) -> Optional[ast.AST]:
|
|
137
|
+
"""Parse Python code into AST."""
|
|
138
|
+
try:
|
|
139
|
+
return ast.parse(code)
|
|
140
|
+
except SyntaxError:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
def analyze(self, code: str) -> AnalysisResult:
|
|
144
|
+
"""Analyze Python code."""
|
|
145
|
+
tree = self.parse(code)
|
|
146
|
+
parse_errors = []
|
|
147
|
+
|
|
148
|
+
if tree is None:
|
|
149
|
+
# Try to extract what we can with regex
|
|
150
|
+
parse_errors.append("Failed to parse Python AST")
|
|
151
|
+
return AnalysisResult(
|
|
152
|
+
language=self.language,
|
|
153
|
+
functions=self._extract_functions_regex(code),
|
|
154
|
+
imports=self._extract_imports_regex(code),
|
|
155
|
+
strings=self._extract_strings_regex(code),
|
|
156
|
+
variables={},
|
|
157
|
+
dangerous_calls=self._find_dangerous_calls(code),
|
|
158
|
+
parse_errors=parse_errors,
|
|
159
|
+
raw_ast=None,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return AnalysisResult(
|
|
163
|
+
language=self.language,
|
|
164
|
+
functions=self._extract_functions(tree),
|
|
165
|
+
imports=self._extract_imports(tree),
|
|
166
|
+
strings=self._extract_strings(tree, code),
|
|
167
|
+
variables=self._extract_variables(tree),
|
|
168
|
+
dangerous_calls=self._find_dangerous_calls_ast(tree),
|
|
169
|
+
parse_errors=parse_errors,
|
|
170
|
+
raw_ast=tree,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _extract_functions(self, tree: ast.AST) -> List[FunctionInfo]:
|
|
174
|
+
"""Extract function definitions from AST."""
|
|
175
|
+
functions = []
|
|
176
|
+
|
|
177
|
+
for node in ast.walk(tree):
|
|
178
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
179
|
+
# Get decorator names
|
|
180
|
+
decorators = []
|
|
181
|
+
for dec in node.decorator_list:
|
|
182
|
+
if isinstance(dec, ast.Name):
|
|
183
|
+
decorators.append(dec.id)
|
|
184
|
+
elif isinstance(dec, ast.Attribute):
|
|
185
|
+
decorators.append(f"{self._get_full_name(dec)}")
|
|
186
|
+
elif isinstance(dec, ast.Call):
|
|
187
|
+
if isinstance(dec.func, ast.Name):
|
|
188
|
+
decorators.append(dec.func.id)
|
|
189
|
+
elif isinstance(dec.func, ast.Attribute):
|
|
190
|
+
decorators.append(self._get_full_name(dec.func))
|
|
191
|
+
|
|
192
|
+
# Get docstring
|
|
193
|
+
docstring = ast.get_docstring(node)
|
|
194
|
+
|
|
195
|
+
# Get function calls within this function
|
|
196
|
+
calls = []
|
|
197
|
+
for child in ast.walk(node):
|
|
198
|
+
if isinstance(child, ast.Call):
|
|
199
|
+
call_name = self._get_call_name(child)
|
|
200
|
+
if call_name:
|
|
201
|
+
calls.append(call_name)
|
|
202
|
+
|
|
203
|
+
functions.append(FunctionInfo(
|
|
204
|
+
name=node.name,
|
|
205
|
+
line=node.lineno,
|
|
206
|
+
end_line=getattr(node, 'end_lineno', None),
|
|
207
|
+
args=[arg.arg for arg in node.args.args],
|
|
208
|
+
decorators=decorators,
|
|
209
|
+
is_async=isinstance(node, ast.AsyncFunctionDef),
|
|
210
|
+
docstring=docstring,
|
|
211
|
+
calls=calls,
|
|
212
|
+
))
|
|
213
|
+
|
|
214
|
+
return functions
|
|
215
|
+
|
|
216
|
+
def _extract_imports(self, tree: ast.AST) -> List[ImportInfo]:
|
|
217
|
+
"""Extract import statements from AST."""
|
|
218
|
+
imports = []
|
|
219
|
+
|
|
220
|
+
for node in ast.walk(tree):
|
|
221
|
+
if isinstance(node, ast.Import):
|
|
222
|
+
for alias in node.names:
|
|
223
|
+
imports.append(ImportInfo(
|
|
224
|
+
module=alias.name,
|
|
225
|
+
names=[],
|
|
226
|
+
alias=alias.asname,
|
|
227
|
+
line=node.lineno,
|
|
228
|
+
))
|
|
229
|
+
elif isinstance(node, ast.ImportFrom):
|
|
230
|
+
module = node.module or ""
|
|
231
|
+
names = [alias.name for alias in node.names]
|
|
232
|
+
imports.append(ImportInfo(
|
|
233
|
+
module=module,
|
|
234
|
+
names=names,
|
|
235
|
+
alias=None,
|
|
236
|
+
line=node.lineno,
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
return imports
|
|
240
|
+
|
|
241
|
+
def _extract_strings(self, tree: ast.AST, code: str) -> List[StringLiteral]:
|
|
242
|
+
"""Extract string literals from AST."""
|
|
243
|
+
strings = []
|
|
244
|
+
lines = code.split("\n")
|
|
245
|
+
|
|
246
|
+
for node in ast.walk(tree):
|
|
247
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
248
|
+
# Determine context
|
|
249
|
+
context = self._determine_string_context(node, tree, lines)
|
|
250
|
+
strings.append(StringLiteral(
|
|
251
|
+
value=node.value,
|
|
252
|
+
line=node.lineno,
|
|
253
|
+
column=getattr(node, 'col_offset', None),
|
|
254
|
+
is_fstring=False,
|
|
255
|
+
context=context,
|
|
256
|
+
))
|
|
257
|
+
elif isinstance(node, ast.JoinedStr): # f-string
|
|
258
|
+
# Reconstruct f-string value
|
|
259
|
+
parts = []
|
|
260
|
+
for value in node.values:
|
|
261
|
+
if isinstance(value, ast.Constant):
|
|
262
|
+
parts.append(str(value.value))
|
|
263
|
+
elif isinstance(value, ast.FormattedValue):
|
|
264
|
+
parts.append("{...}")
|
|
265
|
+
|
|
266
|
+
strings.append(StringLiteral(
|
|
267
|
+
value="".join(parts),
|
|
268
|
+
line=node.lineno,
|
|
269
|
+
column=getattr(node, 'col_offset', None),
|
|
270
|
+
is_fstring=True,
|
|
271
|
+
context=self._determine_string_context(node, tree, lines),
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
return strings
|
|
275
|
+
|
|
276
|
+
def _determine_string_context(
|
|
277
|
+
self, node: ast.AST, tree: ast.AST, lines: List[str]
|
|
278
|
+
) -> Optional[str]:
|
|
279
|
+
"""Determine the context of a string (SQL, command, path, etc.)."""
|
|
280
|
+
line_content = lines[node.lineno - 1].lower() if node.lineno <= len(lines) else ""
|
|
281
|
+
|
|
282
|
+
# Check for SQL keywords
|
|
283
|
+
sql_keywords = ["select", "insert", "update", "delete", "drop", "create", "alter"]
|
|
284
|
+
if any(kw in line_content for kw in sql_keywords):
|
|
285
|
+
return "sql_query"
|
|
286
|
+
|
|
287
|
+
# Check for file path indicators
|
|
288
|
+
if any(p in line_content for p in ["open(", "file(", "path", "filename"]):
|
|
289
|
+
return "file_path"
|
|
290
|
+
|
|
291
|
+
# Check for command execution
|
|
292
|
+
if any(c in line_content for c in ["os.system", "subprocess", "popen", "exec"]):
|
|
293
|
+
return "command"
|
|
294
|
+
|
|
295
|
+
# Check for URL
|
|
296
|
+
if any(u in line_content for u in ["http://", "https://", "url", "requests"]):
|
|
297
|
+
return "url"
|
|
298
|
+
|
|
299
|
+
return None
|
|
300
|
+
|
|
301
|
+
def _extract_variables(self, tree: ast.AST) -> Dict[str, List[int]]:
|
|
302
|
+
"""Extract variable assignments."""
|
|
303
|
+
variables: Dict[str, List[int]] = {}
|
|
304
|
+
|
|
305
|
+
for node in ast.walk(tree):
|
|
306
|
+
if isinstance(node, ast.Assign):
|
|
307
|
+
for target in node.targets:
|
|
308
|
+
if isinstance(target, ast.Name):
|
|
309
|
+
if target.id not in variables:
|
|
310
|
+
variables[target.id] = []
|
|
311
|
+
variables[target.id].append(node.lineno)
|
|
312
|
+
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
313
|
+
name = node.target.id
|
|
314
|
+
if name not in variables:
|
|
315
|
+
variables[name] = []
|
|
316
|
+
variables[name].append(node.lineno)
|
|
317
|
+
|
|
318
|
+
return variables
|
|
319
|
+
|
|
320
|
+
def _find_dangerous_calls_ast(self, tree: ast.AST) -> List[Tuple[str, int]]:
|
|
321
|
+
"""Find dangerous function calls using AST."""
|
|
322
|
+
calls = []
|
|
323
|
+
|
|
324
|
+
for node in ast.walk(tree):
|
|
325
|
+
if isinstance(node, ast.Call):
|
|
326
|
+
call_name = self._get_call_name(node)
|
|
327
|
+
if call_name:
|
|
328
|
+
# Check if it's in our dangerous list
|
|
329
|
+
for dangerous in self.DANGEROUS_FUNCTIONS:
|
|
330
|
+
if call_name == dangerous or call_name.endswith(f".{dangerous}"):
|
|
331
|
+
calls.append((call_name, node.lineno))
|
|
332
|
+
break
|
|
333
|
+
# Also check if the last part matches
|
|
334
|
+
if "." in dangerous:
|
|
335
|
+
_, func_name = dangerous.rsplit(".", 1)
|
|
336
|
+
if call_name.endswith(func_name):
|
|
337
|
+
calls.append((call_name, node.lineno))
|
|
338
|
+
break
|
|
339
|
+
|
|
340
|
+
return calls
|
|
341
|
+
|
|
342
|
+
def _get_call_name(self, node: ast.Call) -> Optional[str]:
|
|
343
|
+
"""Get the full name of a function call."""
|
|
344
|
+
if isinstance(node.func, ast.Name):
|
|
345
|
+
return node.func.id
|
|
346
|
+
elif isinstance(node.func, ast.Attribute):
|
|
347
|
+
return self._get_full_name(node.func)
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def _get_full_name(self, node: ast.Attribute) -> str:
|
|
351
|
+
"""Get full dotted name from Attribute node."""
|
|
352
|
+
parts = []
|
|
353
|
+
current = node
|
|
354
|
+
|
|
355
|
+
while isinstance(current, ast.Attribute):
|
|
356
|
+
parts.append(current.attr)
|
|
357
|
+
current = current.value
|
|
358
|
+
|
|
359
|
+
if isinstance(current, ast.Name):
|
|
360
|
+
parts.append(current.id)
|
|
361
|
+
|
|
362
|
+
return ".".join(reversed(parts))
|
|
363
|
+
|
|
364
|
+
def _extract_functions_regex(self, code: str) -> List[FunctionInfo]:
|
|
365
|
+
"""Fallback regex-based function extraction."""
|
|
366
|
+
functions = []
|
|
367
|
+
pattern = r"^\s*(async\s+)?def\s+(\w+)\s*\((.*?)\)"
|
|
368
|
+
|
|
369
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
370
|
+
match = re.match(pattern, line)
|
|
371
|
+
if match:
|
|
372
|
+
is_async = bool(match.group(1))
|
|
373
|
+
name = match.group(2)
|
|
374
|
+
args_str = match.group(3)
|
|
375
|
+
args = [a.strip().split(":")[0].split("=")[0].strip()
|
|
376
|
+
for a in args_str.split(",") if a.strip()]
|
|
377
|
+
|
|
378
|
+
functions.append(FunctionInfo(
|
|
379
|
+
name=name,
|
|
380
|
+
line=i,
|
|
381
|
+
end_line=None,
|
|
382
|
+
args=args,
|
|
383
|
+
decorators=[],
|
|
384
|
+
is_async=is_async,
|
|
385
|
+
docstring=None,
|
|
386
|
+
calls=[],
|
|
387
|
+
))
|
|
388
|
+
|
|
389
|
+
return functions
|
|
390
|
+
|
|
391
|
+
def _extract_imports_regex(self, code: str) -> List[ImportInfo]:
|
|
392
|
+
"""Fallback regex-based import extraction."""
|
|
393
|
+
imports = []
|
|
394
|
+
|
|
395
|
+
# import module
|
|
396
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
397
|
+
match = re.match(r"^\s*import\s+([\w.]+)(?:\s+as\s+(\w+))?", line)
|
|
398
|
+
if match:
|
|
399
|
+
imports.append(ImportInfo(
|
|
400
|
+
module=match.group(1),
|
|
401
|
+
names=[],
|
|
402
|
+
alias=match.group(2),
|
|
403
|
+
line=i,
|
|
404
|
+
))
|
|
405
|
+
|
|
406
|
+
# from module import names
|
|
407
|
+
match = re.match(r"^\s*from\s+([\w.]+)\s+import\s+(.+)", line)
|
|
408
|
+
if match:
|
|
409
|
+
names = [n.strip().split(" as ")[0] for n in match.group(2).split(",")]
|
|
410
|
+
imports.append(ImportInfo(
|
|
411
|
+
module=match.group(1),
|
|
412
|
+
names=names,
|
|
413
|
+
alias=None,
|
|
414
|
+
line=i,
|
|
415
|
+
))
|
|
416
|
+
|
|
417
|
+
return imports
|
|
418
|
+
|
|
419
|
+
def _extract_strings_regex(self, code: str) -> List[StringLiteral]:
|
|
420
|
+
"""Fallback regex-based string extraction."""
|
|
421
|
+
strings = []
|
|
422
|
+
|
|
423
|
+
# Match various string patterns
|
|
424
|
+
patterns = [
|
|
425
|
+
(r'f["\']([^"\']*)["\']', True), # f-strings
|
|
426
|
+
(r'["\']([^"\']{3,})["\']', False), # Regular strings (3+ chars)
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
430
|
+
for pattern, is_fstring in patterns:
|
|
431
|
+
for match in re.finditer(pattern, line):
|
|
432
|
+
strings.append(StringLiteral(
|
|
433
|
+
value=match.group(1),
|
|
434
|
+
line=i,
|
|
435
|
+
column=match.start(),
|
|
436
|
+
is_fstring=is_fstring,
|
|
437
|
+
context=None,
|
|
438
|
+
))
|
|
439
|
+
|
|
440
|
+
return strings
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
class JavaScriptAnalyzer(LanguageAnalyzer):
|
|
444
|
+
"""JavaScript/TypeScript analyzer using regex patterns."""
|
|
445
|
+
|
|
446
|
+
language = "javascript"
|
|
447
|
+
|
|
448
|
+
DANGEROUS_FUNCTIONS = [
|
|
449
|
+
# Code execution
|
|
450
|
+
"eval", "Function", "setTimeout", "setInterval",
|
|
451
|
+
# Command execution
|
|
452
|
+
"exec", "execSync", "spawn", "spawnSync", "execFile",
|
|
453
|
+
"child_process.exec", "child_process.spawn",
|
|
454
|
+
# DOM manipulation (XSS)
|
|
455
|
+
"innerHTML", "outerHTML", "document.write", "document.writeln",
|
|
456
|
+
# SQL (common ORMs)
|
|
457
|
+
"query", "raw", "execute",
|
|
458
|
+
# Deserialization
|
|
459
|
+
"JSON.parse",
|
|
460
|
+
]
|
|
461
|
+
|
|
462
|
+
def parse(self, code: str) -> Optional[Dict[str, Any]]:
|
|
463
|
+
"""Parse JavaScript using regex (tree-sitter optional)."""
|
|
464
|
+
# Basic structural analysis without full parsing
|
|
465
|
+
return {"code": code}
|
|
466
|
+
|
|
467
|
+
def analyze(self, code: str) -> AnalysisResult:
|
|
468
|
+
"""Analyze JavaScript code."""
|
|
469
|
+
return AnalysisResult(
|
|
470
|
+
language=self.language,
|
|
471
|
+
functions=self._extract_functions(code),
|
|
472
|
+
imports=self._extract_imports(code),
|
|
473
|
+
strings=self._extract_strings(code),
|
|
474
|
+
variables=self._extract_variables(code),
|
|
475
|
+
dangerous_calls=self._find_dangerous_calls(code),
|
|
476
|
+
parse_errors=[],
|
|
477
|
+
raw_ast=None,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def _extract_functions(self, code: str) -> List[FunctionInfo]:
|
|
481
|
+
"""Extract function definitions."""
|
|
482
|
+
functions = []
|
|
483
|
+
|
|
484
|
+
patterns = [
|
|
485
|
+
# function name(args)
|
|
486
|
+
r"function\s+(\w+)\s*\((.*?)\)",
|
|
487
|
+
# const/let/var name = function(args)
|
|
488
|
+
r"(?:const|let|var)\s+(\w+)\s*=\s*function\s*\((.*?)\)",
|
|
489
|
+
# const/let/var name = (args) =>
|
|
490
|
+
r"(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\((.*?)\)\s*=>",
|
|
491
|
+
# async function name(args)
|
|
492
|
+
r"async\s+function\s+(\w+)\s*\((.*?)\)",
|
|
493
|
+
]
|
|
494
|
+
|
|
495
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
496
|
+
for pattern in patterns:
|
|
497
|
+
match = re.search(pattern, line)
|
|
498
|
+
if match:
|
|
499
|
+
name = match.group(1)
|
|
500
|
+
args_str = match.group(2)
|
|
501
|
+
args = [a.strip().split(":")[0].split("=")[0].strip()
|
|
502
|
+
for a in args_str.split(",") if a.strip()]
|
|
503
|
+
|
|
504
|
+
is_async = "async" in line[:match.start()]
|
|
505
|
+
|
|
506
|
+
functions.append(FunctionInfo(
|
|
507
|
+
name=name,
|
|
508
|
+
line=i,
|
|
509
|
+
end_line=None,
|
|
510
|
+
args=args,
|
|
511
|
+
decorators=[],
|
|
512
|
+
is_async=is_async,
|
|
513
|
+
docstring=None,
|
|
514
|
+
calls=[],
|
|
515
|
+
))
|
|
516
|
+
break
|
|
517
|
+
|
|
518
|
+
return functions
|
|
519
|
+
|
|
520
|
+
def _extract_imports(self, code: str) -> List[ImportInfo]:
|
|
521
|
+
"""Extract import statements."""
|
|
522
|
+
imports = []
|
|
523
|
+
|
|
524
|
+
patterns = [
|
|
525
|
+
# import x from 'module'
|
|
526
|
+
r"import\s+(\w+)\s+from\s+['\"]([^'\"]+)['\"]",
|
|
527
|
+
# import { x, y } from 'module'
|
|
528
|
+
r"import\s*\{([^}]+)\}\s*from\s*['\"]([^'\"]+)['\"]",
|
|
529
|
+
# const x = require('module')
|
|
530
|
+
r"(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(['\"]([^'\"]+)['\"]\)",
|
|
531
|
+
]
|
|
532
|
+
|
|
533
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
534
|
+
for pattern in patterns:
|
|
535
|
+
match = re.search(pattern, line)
|
|
536
|
+
if match:
|
|
537
|
+
if "{" in pattern:
|
|
538
|
+
names = [n.strip() for n in match.group(1).split(",")]
|
|
539
|
+
module = match.group(2)
|
|
540
|
+
else:
|
|
541
|
+
names = [match.group(1)]
|
|
542
|
+
module = match.group(2)
|
|
543
|
+
|
|
544
|
+
imports.append(ImportInfo(
|
|
545
|
+
module=module,
|
|
546
|
+
names=names,
|
|
547
|
+
alias=None,
|
|
548
|
+
line=i,
|
|
549
|
+
))
|
|
550
|
+
|
|
551
|
+
return imports
|
|
552
|
+
|
|
553
|
+
def _extract_strings(self, code: str) -> List[StringLiteral]:
|
|
554
|
+
"""Extract string literals."""
|
|
555
|
+
strings = []
|
|
556
|
+
|
|
557
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
558
|
+
# Template literals
|
|
559
|
+
for match in re.finditer(r"`([^`]*)`", line):
|
|
560
|
+
strings.append(StringLiteral(
|
|
561
|
+
value=match.group(1),
|
|
562
|
+
line=i,
|
|
563
|
+
column=match.start(),
|
|
564
|
+
is_fstring="${" in match.group(1),
|
|
565
|
+
context=None,
|
|
566
|
+
))
|
|
567
|
+
|
|
568
|
+
# Regular strings
|
|
569
|
+
for match in re.finditer(r"['\"]([^'\"]{3,})['\"]", line):
|
|
570
|
+
if not re.search(r"['\"]" + re.escape(match.group(1)) + r"['\"]", line[:match.start()]):
|
|
571
|
+
strings.append(StringLiteral(
|
|
572
|
+
value=match.group(1),
|
|
573
|
+
line=i,
|
|
574
|
+
column=match.start(),
|
|
575
|
+
is_fstring=False,
|
|
576
|
+
context=None,
|
|
577
|
+
))
|
|
578
|
+
|
|
579
|
+
return strings
|
|
580
|
+
|
|
581
|
+
def _extract_variables(self, code: str) -> Dict[str, List[int]]:
|
|
582
|
+
"""Extract variable assignments."""
|
|
583
|
+
variables: Dict[str, List[int]] = {}
|
|
584
|
+
|
|
585
|
+
pattern = r"(?:const|let|var)\s+(\w+)\s*="
|
|
586
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
587
|
+
for match in re.finditer(pattern, line):
|
|
588
|
+
name = match.group(1)
|
|
589
|
+
if name not in variables:
|
|
590
|
+
variables[name] = []
|
|
591
|
+
variables[name].append(i)
|
|
592
|
+
|
|
593
|
+
return variables
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
class JavaAnalyzer(LanguageAnalyzer):
|
|
597
|
+
"""Java analyzer using regex patterns."""
|
|
598
|
+
|
|
599
|
+
language = "java"
|
|
600
|
+
|
|
601
|
+
DANGEROUS_FUNCTIONS = [
|
|
602
|
+
# Command execution
|
|
603
|
+
"Runtime.exec", "ProcessBuilder",
|
|
604
|
+
# SQL
|
|
605
|
+
"executeQuery", "executeUpdate", "prepareStatement",
|
|
606
|
+
"createStatement", "Statement.execute",
|
|
607
|
+
# Deserialization
|
|
608
|
+
"ObjectInputStream.readObject", "XMLDecoder.readObject",
|
|
609
|
+
# File operations
|
|
610
|
+
"FileInputStream", "FileOutputStream", "FileReader", "FileWriter",
|
|
611
|
+
# Crypto (weak)
|
|
612
|
+
"MessageDigest.getInstance",
|
|
613
|
+
]
|
|
614
|
+
|
|
615
|
+
def parse(self, code: str) -> Optional[Dict[str, Any]]:
|
|
616
|
+
"""Parse Java using regex."""
|
|
617
|
+
return {"code": code}
|
|
618
|
+
|
|
619
|
+
def analyze(self, code: str) -> AnalysisResult:
|
|
620
|
+
"""Analyze Java code."""
|
|
621
|
+
return AnalysisResult(
|
|
622
|
+
language=self.language,
|
|
623
|
+
functions=self._extract_functions(code),
|
|
624
|
+
imports=self._extract_imports(code),
|
|
625
|
+
strings=self._extract_strings(code),
|
|
626
|
+
variables=self._extract_variables(code),
|
|
627
|
+
dangerous_calls=self._find_dangerous_calls(code),
|
|
628
|
+
parse_errors=[],
|
|
629
|
+
raw_ast=None,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
def _extract_functions(self, code: str) -> List[FunctionInfo]:
|
|
633
|
+
"""Extract method definitions."""
|
|
634
|
+
functions = []
|
|
635
|
+
|
|
636
|
+
pattern = r"(?:public|private|protected)?\s*(?:static)?\s*(?:\w+)\s+(\w+)\s*\((.*?)\)"
|
|
637
|
+
|
|
638
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
639
|
+
match = re.search(pattern, line)
|
|
640
|
+
if match and match.group(1) not in ["if", "while", "for", "switch"]:
|
|
641
|
+
name = match.group(1)
|
|
642
|
+
args_str = match.group(2)
|
|
643
|
+
args = []
|
|
644
|
+
if args_str.strip():
|
|
645
|
+
for arg in args_str.split(","):
|
|
646
|
+
parts = arg.strip().split()
|
|
647
|
+
if len(parts) >= 2:
|
|
648
|
+
args.append(parts[-1])
|
|
649
|
+
|
|
650
|
+
functions.append(FunctionInfo(
|
|
651
|
+
name=name,
|
|
652
|
+
line=i,
|
|
653
|
+
end_line=None,
|
|
654
|
+
args=args,
|
|
655
|
+
decorators=[],
|
|
656
|
+
is_async=False,
|
|
657
|
+
docstring=None,
|
|
658
|
+
calls=[],
|
|
659
|
+
))
|
|
660
|
+
|
|
661
|
+
return functions
|
|
662
|
+
|
|
663
|
+
def _extract_imports(self, code: str) -> List[ImportInfo]:
|
|
664
|
+
"""Extract import statements."""
|
|
665
|
+
imports = []
|
|
666
|
+
|
|
667
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
668
|
+
match = re.match(r"import\s+([\w.]+);", line.strip())
|
|
669
|
+
if match:
|
|
670
|
+
imports.append(ImportInfo(
|
|
671
|
+
module=match.group(1),
|
|
672
|
+
names=[],
|
|
673
|
+
alias=None,
|
|
674
|
+
line=i,
|
|
675
|
+
))
|
|
676
|
+
|
|
677
|
+
return imports
|
|
678
|
+
|
|
679
|
+
def _extract_strings(self, code: str) -> List[StringLiteral]:
|
|
680
|
+
"""Extract string literals."""
|
|
681
|
+
strings = []
|
|
682
|
+
|
|
683
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
684
|
+
for match in re.finditer(r'"([^"]{3,})"', line):
|
|
685
|
+
strings.append(StringLiteral(
|
|
686
|
+
value=match.group(1),
|
|
687
|
+
line=i,
|
|
688
|
+
column=match.start(),
|
|
689
|
+
is_fstring=False,
|
|
690
|
+
context=None,
|
|
691
|
+
))
|
|
692
|
+
|
|
693
|
+
return strings
|
|
694
|
+
|
|
695
|
+
def _extract_variables(self, code: str) -> Dict[str, List[int]]:
|
|
696
|
+
"""Extract variable declarations."""
|
|
697
|
+
variables: Dict[str, List[int]] = {}
|
|
698
|
+
|
|
699
|
+
pattern = r"(?:final\s+)?(?:\w+)\s+(\w+)\s*="
|
|
700
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
701
|
+
for match in re.finditer(pattern, line):
|
|
702
|
+
name = match.group(1)
|
|
703
|
+
if name not in variables:
|
|
704
|
+
variables[name] = []
|
|
705
|
+
variables[name].append(i)
|
|
706
|
+
|
|
707
|
+
return variables
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
class GoAnalyzer(LanguageAnalyzer):
|
|
711
|
+
"""Go analyzer using regex patterns."""
|
|
712
|
+
|
|
713
|
+
language = "go"
|
|
714
|
+
|
|
715
|
+
DANGEROUS_FUNCTIONS = [
|
|
716
|
+
# Command execution
|
|
717
|
+
"exec.Command", "os/exec.Command",
|
|
718
|
+
# SQL
|
|
719
|
+
"db.Query", "db.Exec", "db.QueryRow",
|
|
720
|
+
# File operations
|
|
721
|
+
"os.Open", "os.Create", "ioutil.ReadFile", "ioutil.WriteFile",
|
|
722
|
+
# Crypto (weak)
|
|
723
|
+
"md5.New", "sha1.New",
|
|
724
|
+
]
|
|
725
|
+
|
|
726
|
+
def parse(self, code: str) -> Optional[Dict[str, Any]]:
|
|
727
|
+
"""Parse Go using regex."""
|
|
728
|
+
return {"code": code}
|
|
729
|
+
|
|
730
|
+
def analyze(self, code: str) -> AnalysisResult:
|
|
731
|
+
"""Analyze Go code."""
|
|
732
|
+
return AnalysisResult(
|
|
733
|
+
language=self.language,
|
|
734
|
+
functions=self._extract_functions(code),
|
|
735
|
+
imports=self._extract_imports(code),
|
|
736
|
+
strings=self._extract_strings(code),
|
|
737
|
+
variables=self._extract_variables(code),
|
|
738
|
+
dangerous_calls=self._find_dangerous_calls(code),
|
|
739
|
+
parse_errors=[],
|
|
740
|
+
raw_ast=None,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
def _extract_functions(self, code: str) -> List[FunctionInfo]:
|
|
744
|
+
"""Extract function definitions."""
|
|
745
|
+
functions = []
|
|
746
|
+
|
|
747
|
+
pattern = r"func\s+(?:\([^)]+\)\s+)?(\w+)\s*\((.*?)\)"
|
|
748
|
+
|
|
749
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
750
|
+
match = re.search(pattern, line)
|
|
751
|
+
if match:
|
|
752
|
+
name = match.group(1)
|
|
753
|
+
args_str = match.group(2)
|
|
754
|
+
args = []
|
|
755
|
+
if args_str.strip():
|
|
756
|
+
for arg in args_str.split(","):
|
|
757
|
+
parts = arg.strip().split()
|
|
758
|
+
if parts:
|
|
759
|
+
args.append(parts[0])
|
|
760
|
+
|
|
761
|
+
functions.append(FunctionInfo(
|
|
762
|
+
name=name,
|
|
763
|
+
line=i,
|
|
764
|
+
end_line=None,
|
|
765
|
+
args=args,
|
|
766
|
+
decorators=[],
|
|
767
|
+
is_async=False,
|
|
768
|
+
docstring=None,
|
|
769
|
+
calls=[],
|
|
770
|
+
))
|
|
771
|
+
|
|
772
|
+
return functions
|
|
773
|
+
|
|
774
|
+
def _extract_imports(self, code: str) -> List[ImportInfo]:
|
|
775
|
+
"""Extract import statements."""
|
|
776
|
+
imports = []
|
|
777
|
+
|
|
778
|
+
# Single import
|
|
779
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
780
|
+
match = re.match(r'import\s+"([^"]+)"', line.strip())
|
|
781
|
+
if match:
|
|
782
|
+
imports.append(ImportInfo(
|
|
783
|
+
module=match.group(1),
|
|
784
|
+
names=[],
|
|
785
|
+
alias=None,
|
|
786
|
+
line=i,
|
|
787
|
+
))
|
|
788
|
+
|
|
789
|
+
# Multi-line import block
|
|
790
|
+
in_import = False
|
|
791
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
792
|
+
if "import (" in line:
|
|
793
|
+
in_import = True
|
|
794
|
+
continue
|
|
795
|
+
if in_import:
|
|
796
|
+
if ")" in line:
|
|
797
|
+
in_import = False
|
|
798
|
+
continue
|
|
799
|
+
match = re.search(r'"([^"]+)"', line)
|
|
800
|
+
if match:
|
|
801
|
+
imports.append(ImportInfo(
|
|
802
|
+
module=match.group(1),
|
|
803
|
+
names=[],
|
|
804
|
+
alias=None,
|
|
805
|
+
line=i,
|
|
806
|
+
))
|
|
807
|
+
|
|
808
|
+
return imports
|
|
809
|
+
|
|
810
|
+
def _extract_strings(self, code: str) -> List[StringLiteral]:
|
|
811
|
+
"""Extract string literals."""
|
|
812
|
+
strings = []
|
|
813
|
+
|
|
814
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
815
|
+
# Double-quoted strings
|
|
816
|
+
for match in re.finditer(r'"([^"]{3,})"', line):
|
|
817
|
+
strings.append(StringLiteral(
|
|
818
|
+
value=match.group(1),
|
|
819
|
+
line=i,
|
|
820
|
+
column=match.start(),
|
|
821
|
+
is_fstring=False,
|
|
822
|
+
context=None,
|
|
823
|
+
))
|
|
824
|
+
|
|
825
|
+
# Backtick strings (raw)
|
|
826
|
+
for match in re.finditer(r"`([^`]{3,})`", line):
|
|
827
|
+
strings.append(StringLiteral(
|
|
828
|
+
value=match.group(1),
|
|
829
|
+
line=i,
|
|
830
|
+
column=match.start(),
|
|
831
|
+
is_fstring=False,
|
|
832
|
+
context=None,
|
|
833
|
+
))
|
|
834
|
+
|
|
835
|
+
return strings
|
|
836
|
+
|
|
837
|
+
def _extract_variables(self, code: str) -> Dict[str, List[int]]:
|
|
838
|
+
"""Extract variable declarations."""
|
|
839
|
+
variables: Dict[str, List[int]] = {}
|
|
840
|
+
|
|
841
|
+
patterns = [
|
|
842
|
+
r"(\w+)\s*:=", # Short declaration
|
|
843
|
+
r"var\s+(\w+)", # Var declaration
|
|
844
|
+
]
|
|
845
|
+
|
|
846
|
+
for i, line in enumerate(code.split("\n"), 1):
|
|
847
|
+
for pattern in patterns:
|
|
848
|
+
for match in re.finditer(pattern, line):
|
|
849
|
+
name = match.group(1)
|
|
850
|
+
if name not in variables:
|
|
851
|
+
variables[name] = []
|
|
852
|
+
variables[name].append(i)
|
|
853
|
+
|
|
854
|
+
return variables
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
class CodeAnalyzer:
|
|
858
|
+
"""
|
|
859
|
+
Main code analyzer that delegates to language-specific analyzers.
|
|
860
|
+
|
|
861
|
+
Usage:
|
|
862
|
+
analyzer = CodeAnalyzer()
|
|
863
|
+
result = analyzer.analyze(code, "python")
|
|
864
|
+
|
|
865
|
+
# Auto-detect language
|
|
866
|
+
result = analyzer.analyze(code)
|
|
867
|
+
"""
|
|
868
|
+
|
|
869
|
+
ANALYZERS: Dict[str, type] = {
|
|
870
|
+
"python": PythonAnalyzer,
|
|
871
|
+
"py": PythonAnalyzer,
|
|
872
|
+
"javascript": JavaScriptAnalyzer,
|
|
873
|
+
"js": JavaScriptAnalyzer,
|
|
874
|
+
"typescript": JavaScriptAnalyzer,
|
|
875
|
+
"ts": JavaScriptAnalyzer,
|
|
876
|
+
"java": JavaAnalyzer,
|
|
877
|
+
"go": GoAnalyzer,
|
|
878
|
+
"golang": GoAnalyzer,
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
# Language detection patterns
|
|
882
|
+
LANGUAGE_PATTERNS: Dict[str, List[str]] = {
|
|
883
|
+
"python": [
|
|
884
|
+
r"^\s*def\s+\w+\s*\(",
|
|
885
|
+
r"^\s*import\s+\w+",
|
|
886
|
+
r"^\s*from\s+\w+\s+import",
|
|
887
|
+
r"^\s*class\s+\w+.*:",
|
|
888
|
+
r"^\s*if\s+__name__\s*==",
|
|
889
|
+
r"print\s*\(",
|
|
890
|
+
],
|
|
891
|
+
"javascript": [
|
|
892
|
+
r"^\s*const\s+\w+\s*=",
|
|
893
|
+
r"^\s*let\s+\w+\s*=",
|
|
894
|
+
r"^\s*function\s+\w+\s*\(",
|
|
895
|
+
r"=>\s*{",
|
|
896
|
+
r"require\s*\(",
|
|
897
|
+
r"module\.exports",
|
|
898
|
+
r"console\.log",
|
|
899
|
+
],
|
|
900
|
+
"java": [
|
|
901
|
+
r"^\s*public\s+class",
|
|
902
|
+
r"^\s*private\s+\w+",
|
|
903
|
+
r"^\s*import\s+java\.",
|
|
904
|
+
r"System\.out\.println",
|
|
905
|
+
r"public\s+static\s+void\s+main",
|
|
906
|
+
],
|
|
907
|
+
"go": [
|
|
908
|
+
r"^\s*package\s+\w+",
|
|
909
|
+
r"^\s*func\s+\w+\s*\(",
|
|
910
|
+
r"^\s*import\s+\(",
|
|
911
|
+
r"fmt\.Print",
|
|
912
|
+
r":=",
|
|
913
|
+
],
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
def __init__(self):
|
|
917
|
+
self._analyzers: Dict[str, LanguageAnalyzer] = {}
|
|
918
|
+
|
|
919
|
+
def get_analyzer(self, language: str) -> Optional[LanguageAnalyzer]:
|
|
920
|
+
"""Get or create an analyzer for the given language."""
|
|
921
|
+
lang_lower = language.lower()
|
|
922
|
+
if lang_lower not in self._analyzers:
|
|
923
|
+
analyzer_cls = self.ANALYZERS.get(lang_lower)
|
|
924
|
+
if analyzer_cls:
|
|
925
|
+
self._analyzers[lang_lower] = analyzer_cls()
|
|
926
|
+
return self._analyzers.get(lang_lower)
|
|
927
|
+
|
|
928
|
+
def detect_language(self, code: str) -> str:
|
|
929
|
+
"""Auto-detect the programming language of code."""
|
|
930
|
+
scores: Dict[str, int] = {lang: 0 for lang in self.LANGUAGE_PATTERNS}
|
|
931
|
+
|
|
932
|
+
for lang, patterns in self.LANGUAGE_PATTERNS.items():
|
|
933
|
+
for pattern in patterns:
|
|
934
|
+
if re.search(pattern, code, re.MULTILINE):
|
|
935
|
+
scores[lang] += 1
|
|
936
|
+
|
|
937
|
+
# Return language with highest score, default to python
|
|
938
|
+
if max(scores.values()) == 0:
|
|
939
|
+
return "python"
|
|
940
|
+
|
|
941
|
+
return max(scores, key=lambda k: scores[k])
|
|
942
|
+
|
|
943
|
+
def analyze(
|
|
944
|
+
self, code: str, language: Optional[str] = None
|
|
945
|
+
) -> AnalysisResult:
|
|
946
|
+
"""
|
|
947
|
+
Analyze code for security evaluation.
|
|
948
|
+
|
|
949
|
+
Args:
|
|
950
|
+
code: Source code to analyze
|
|
951
|
+
language: Programming language (auto-detected if not provided)
|
|
952
|
+
|
|
953
|
+
Returns:
|
|
954
|
+
AnalysisResult with functions, imports, strings, etc.
|
|
955
|
+
"""
|
|
956
|
+
# Detect language if not provided
|
|
957
|
+
if language is None:
|
|
958
|
+
language = self.detect_language(code)
|
|
959
|
+
|
|
960
|
+
analyzer = self.get_analyzer(language)
|
|
961
|
+
if analyzer is None:
|
|
962
|
+
# Return basic analysis for unsupported languages
|
|
963
|
+
return AnalysisResult(
|
|
964
|
+
language=language,
|
|
965
|
+
functions=[],
|
|
966
|
+
imports=[],
|
|
967
|
+
strings=[],
|
|
968
|
+
variables={},
|
|
969
|
+
dangerous_calls=[],
|
|
970
|
+
parse_errors=[f"Unsupported language: {language}"],
|
|
971
|
+
raw_ast=None,
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
return analyzer.analyze(code)
|
|
975
|
+
|
|
976
|
+
def supports_language(self, language: str) -> bool:
|
|
977
|
+
"""Check if a language is supported."""
|
|
978
|
+
return language.lower() in self.ANALYZERS
|
|
979
|
+
|
|
980
|
+
def supported_languages(self) -> List[str]:
|
|
981
|
+
"""Get list of supported languages."""
|
|
982
|
+
return list(set(
|
|
983
|
+
analyzer_cls.language
|
|
984
|
+
for analyzer_cls in self.ANALYZERS.values()
|
|
985
|
+
))
|