attocode-code-intel 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.
- attocode_code_intel-0.1.0.dist-info/METADATA +221 -0
- attocode_code_intel-0.1.0.dist-info/RECORD +345 -0
- attocode_code_intel-0.1.0.dist-info/WHEEL +4 -0
- attocode_code_intel-0.1.0.dist-info/entry_points.txt +2 -0
- attocode_code_intel-0.1.0.dist-info/licenses/LICENSE +21 -0
- attocode_intel/GUIDELINES.md +432 -0
- attocode_intel/__init__.py +52 -0
- attocode_intel/_internal/__init__.py +1 -0
- attocode_intel/_internal/errors/__init__.py +126 -0
- attocode_intel/_internal/integrations/__init__.py +1 -0
- attocode_intel/_internal/integrations/context/__init__.py +1 -0
- attocode_intel/_internal/integrations/context/architecture_drift.py +354 -0
- attocode_intel/_internal/integrations/context/ast_chunker.py +202 -0
- attocode_intel/_internal/integrations/context/ast_service.py +1483 -0
- attocode_intel/_internal/integrations/context/blob_oid.py +423 -0
- attocode_intel/_internal/integrations/context/cache_manifest.py +221 -0
- attocode_intel/_internal/integrations/context/cas.py +466 -0
- attocode_intel/_internal/integrations/context/code_analyzer.py +528 -0
- attocode_intel/_internal/integrations/context/codebase_ast.py +1461 -0
- attocode_intel/_internal/integrations/context/codebase_context.py +1736 -0
- attocode_intel/_internal/integrations/context/cross_mode.py +263 -0
- attocode_intel/_internal/integrations/context/cross_references.py +616 -0
- attocode_intel/_internal/integrations/context/embedding_rotation.py +597 -0
- attocode_intel/_internal/integrations/context/embeddings.py +282 -0
- attocode_intel/_internal/integrations/context/frecency.py +427 -0
- attocode_intel/_internal/integrations/context/fuzzy.py +320 -0
- attocode_intel/_internal/integrations/context/hierarchical_explorer.py +331 -0
- attocode_intel/_internal/integrations/context/hydration.py +90 -0
- attocode_intel/_internal/integrations/context/index_repair.py +87 -0
- attocode_intel/_internal/integrations/context/index_store.py +586 -0
- attocode_intel/_internal/integrations/context/memory_store.py +602 -0
- attocode_intel/_internal/integrations/context/module_resolution.py +52 -0
- attocode_intel/_internal/integrations/context/query_constraints.py +361 -0
- attocode_intel/_internal/integrations/context/query_history.py +417 -0
- attocode_intel/_internal/integrations/context/reranker.py +351 -0
- attocode_intel/_internal/integrations/context/semantic_search.py +2931 -0
- attocode_intel/_internal/integrations/context/syntax_evidence.py +94 -0
- attocode_intel/_internal/integrations/context/systemone_ranker.py +245 -0
- attocode_intel/_internal/integrations/context/temporal_coupling.py +411 -0
- attocode_intel/_internal/integrations/context/trigram_index.py +820 -0
- attocode_intel/_internal/integrations/context/trigram_regex.py +171 -0
- attocode_intel/_internal/integrations/context/ts_parser.py +2199 -0
- attocode_intel/_internal/integrations/context/vector_store.py +795 -0
- attocode_intel/_internal/integrations/feature_flags/__init__.py +357 -0
- attocode_intel/_internal/integrations/lsp/__init__.py +1 -0
- attocode_intel/_internal/integrations/lsp/client.py +1140 -0
- attocode_intel/_internal/integrations/mcp/__init__.py +1 -0
- attocode_intel/_internal/integrations/mcp/client.py +253 -0
- attocode_intel/_internal/integrations/security/__init__.py +1 -0
- attocode_intel/_internal/integrations/security/custom_rules.py +176 -0
- attocode_intel/_internal/integrations/security/dataflow.py +522 -0
- attocode_intel/_internal/integrations/security/dependency_audit.py +303 -0
- attocode_intel/_internal/integrations/security/matcher.py +44 -0
- attocode_intel/_internal/integrations/security/patterns.py +724 -0
- attocode_intel/_internal/integrations/security/scanner.py +351 -0
- attocode_intel/_internal/integrations/utilities/__init__.py +1 -0
- attocode_intel/_internal/integrations/utilities/ignore.py +64 -0
- attocode_intel/_internal/integrations/utilities/token_estimate.py +51 -0
- attocode_intel/_internal/tricks/__init__.py +1 -0
- attocode_intel/_internal/tricks/recursive_context.py +357 -0
- attocode_intel/_internal/types/__init__.py +1 -0
- attocode_intel/_internal/types/events.py +289 -0
- attocode_intel/_internal/types/severity.py +15 -0
- attocode_intel/_shared.py +341 -0
- attocode_intel/analysis_coverage.py +56 -0
- attocode_intel/api/__init__.py +1 -0
- attocode_intel/api/access.py +32 -0
- attocode_intel/api/app.py +247 -0
- attocode_intel/api/auth/__init__.py +128 -0
- attocode_intel/api/auth/api_keys.py +61 -0
- attocode_intel/api/auth/context.py +34 -0
- attocode_intel/api/auth/jwt.py +66 -0
- attocode_intel/api/auth/oauth.py +146 -0
- attocode_intel/api/auth/passwords.py +15 -0
- attocode_intel/api/deps.py +351 -0
- attocode_intel/api/knowledge_bridge.py +13 -0
- attocode_intel/api/mcp_transport.py +51 -0
- attocode_intel/api/middleware.py +411 -0
- attocode_intel/api/models.py +698 -0
- attocode_intel/api/providers/__init__.py +1 -0
- attocode_intel/api/providers/db_provider.py +1106 -0
- attocode_intel/api/providers/local_provider.py +476 -0
- attocode_intel/api/providers/remote_provider.py +740 -0
- attocode_intel/api/repository_source.py +21 -0
- attocode_intel/api/routes/__init__.py +1 -0
- attocode_intel/api/routes/_pin_helper.py +158 -0
- attocode_intel/api/routes/activity.py +148 -0
- attocode_intel/api/routes/adr.py +102 -0
- attocode_intel/api/routes/analysis.py +515 -0
- attocode_intel/api/routes/api_keys.py +170 -0
- attocode_intel/api/routes/auth.py +428 -0
- attocode_intel/api/routes/branches.py +262 -0
- attocode_intel/api/routes/cross_repo_search.py +198 -0
- attocode_intel/api/routes/embeddings.py +386 -0
- attocode_intel/api/routes/files.py +132 -0
- attocode_intel/api/routes/files_v2.py +388 -0
- attocode_intel/api/routes/gc.py +322 -0
- attocode_intel/api/routes/git_v2.py +433 -0
- attocode_intel/api/routes/graph.py +174 -0
- attocode_intel/api/routes/graph_viz.py +160 -0
- attocode_intel/api/routes/health.py +62 -0
- attocode_intel/api/routes/history.py +175 -0
- attocode_intel/api/routes/jobs.py +84 -0
- attocode_intel/api/routes/learning.py +205 -0
- attocode_intel/api/routes/lsp.py +272 -0
- attocode_intel/api/routes/notify.py +623 -0
- attocode_intel/api/routes/operations.py +27 -0
- attocode_intel/api/routes/orgs.py +924 -0
- attocode_intel/api/routes/preferences.py +100 -0
- attocode_intel/api/routes/presence.py +42 -0
- attocode_intel/api/routes/projects.py +116 -0
- attocode_intel/api/routes/repos.py +47 -0
- attocode_intel/api/routes/search.py +180 -0
- attocode_intel/api/routes/snapshots.py +614 -0
- attocode_intel/api/routes/webhooks.py +313 -0
- attocode_intel/api/routes/websocket.py +116 -0
- attocode_intel/api/static/graph.html +239 -0
- attocode_intel/api/utils.py +96 -0
- attocode_intel/artifacts/__init__.py +45 -0
- attocode_intel/artifacts/hashing.py +230 -0
- attocode_intel/artifacts/provenance.py +190 -0
- attocode_intel/artifacts/retrieval_pin.py +159 -0
- attocode_intel/audit.py +59 -0
- attocode_intel/bug_finder.py +206 -0
- attocode_intel/bundle.py +80 -0
- attocode_intel/catalog.py +242 -0
- attocode_intel/change_review.py +75 -0
- attocode_intel/cli.py +3062 -0
- attocode_intel/community.py +63 -0
- attocode_intel/confidence/__init__.py +144 -0
- attocode_intel/confidence/jev.py +234 -0
- attocode_intel/confidence/llm.py +330 -0
- attocode_intel/confidence/redact.py +44 -0
- attocode_intel/confidence/settings.py +74 -0
- attocode_intel/config.py +256 -0
- attocode_intel/crypto.py +31 -0
- attocode_intel/db/__init__.py +7 -0
- attocode_intel/db/base.py +33 -0
- attocode_intel/db/engine.py +61 -0
- attocode_intel/db/models.py +529 -0
- attocode_intel/entrypoint.py +185 -0
- attocode_intel/focused_evidence.py +130 -0
- attocode_intel/freshness.py +154 -0
- attocode_intel/gateway.py +734 -0
- attocode_intel/git/__init__.py +7 -0
- attocode_intel/git/credentials.py +75 -0
- attocode_intel/git/manager.py +493 -0
- attocode_intel/git/models.py +96 -0
- attocode_intel/git/storage.py +98 -0
- attocode_intel/graph_query_parser.py +761 -0
- attocode_intel/helpers.py +712 -0
- attocode_intel/indexing/__init__.py +29 -0
- attocode_intel/indexing/debouncer.py +136 -0
- attocode_intel/indexing/delta_indexer.py +151 -0
- attocode_intel/indexing/full_indexer.py +323 -0
- attocode_intel/indexing/incremental.py +240 -0
- attocode_intel/indexing/parser.py +403 -0
- attocode_intel/indexing/summarizer.py +264 -0
- attocode_intel/installer.py +1896 -0
- attocode_intel/knowledge.py +269 -0
- attocode_intel/local_knowledge.py +77 -0
- attocode_intel/migrations/alembic.ini +36 -0
- attocode_intel/migrations/env.py +71 -0
- attocode_intel/migrations/script.py.mako +27 -0
- attocode_intel/migrations/versions/001_initial_schema.py +219 -0
- attocode_intel/migrations/versions/002_branch_version_and_embeddings.py +61 -0
- attocode_intel/migrations/versions/003_embedding_uniqueness_and_cascade.py +51 -0
- attocode_intel/migrations/versions/004_audit_events.py +38 -0
- attocode_intel/migrations/versions/005_user_preferences.py +30 -0
- attocode_intel/migrations/versions/006_google_oauth.py +27 -0
- attocode_intel/migrations/versions/007_pgvector_embeddings.py +28 -0
- attocode_intel/migrations/versions/008_symbol_references.py +37 -0
- attocode_intel/migrations/versions/009_webhook_secret_encryption.py +25 -0
- attocode_intel/migrations/versions/010_add_commits_table.py +40 -0
- attocode_intel/migrations/versions/011_commit_changed_files.py +25 -0
- attocode_intel/migrations/versions/012_branch_merged_at.py +25 -0
- attocode_intel/migrations/versions/013_revoked_tokens.py +32 -0
- attocode_intel/migrations/versions/014_blame_hunks.py +40 -0
- attocode_intel/migrations/versions/015_commit_file_stats.py +40 -0
- attocode_intel/migrations/versions/016_embedding_provenance_columns.py +179 -0
- attocode_intel/migrations/versions/017_provenance_table.py +102 -0
- attocode_intel/migrations/versions/018_repo_snapshots.py +178 -0
- attocode_intel/migrations/versions/019_symref_caller.py +42 -0
- attocode_intel/migrations/versions/020_shared_knowledge.py +29 -0
- attocode_intel/onboarding.py +258 -0
- attocode_intel/output.py +178 -0
- attocode_intel/precision.py +133 -0
- attocode_intel/presence.py +160 -0
- attocode_intel/probe.py +110 -0
- attocode_intel/project_dir.py +97 -0
- attocode_intel/pubsub.py +113 -0
- attocode_intel/query_ranking.py +242 -0
- attocode_intel/readiness.py +972 -0
- attocode_intel/redis.py +51 -0
- attocode_intel/reference_pages.py +60 -0
- attocode_intel/remote.py +131 -0
- attocode_intel/repo_ranker.py +246 -0
- attocode_intel/request_context.py +82 -0
- attocode_intel/rules/__init__.py +28 -0
- attocode_intel/rules/ci.py +320 -0
- attocode_intel/rules/combinators.py +293 -0
- attocode_intel/rules/enricher.py +127 -0
- attocode_intel/rules/evolution.py +452 -0
- attocode_intel/rules/executor.py +531 -0
- attocode_intel/rules/filters/__init__.py +5 -0
- attocode_intel/rules/filters/pipeline.py +151 -0
- attocode_intel/rules/formatter.py +198 -0
- attocode_intel/rules/hygiene.py +242 -0
- attocode_intel/rules/importers/__init__.py +1 -0
- attocode_intel/rules/importers/semgrep.py +382 -0
- attocode_intel/rules/loader.py +292 -0
- attocode_intel/rules/marketplace.py +375 -0
- attocode_intel/rules/metavar.py +181 -0
- attocode_intel/rules/model.py +317 -0
- attocode_intel/rules/packs/__init__.py +1 -0
- attocode_intel/rules/packs/community/_license_templates/Apache-2.0.txt +176 -0
- attocode_intel/rules/packs/community/_license_templates/MIT.txt +21 -0
- attocode_intel/rules/packs/community/bandit-python/LICENSE +176 -0
- attocode_intel/rules/packs/community/bandit-python/NOTICE +13 -0
- attocode_intel/rules/packs/community/bandit-python/PORTING.md +23 -0
- attocode_intel/rules/packs/community/bandit-python/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B105-hardcoded-password.yaml +23 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B303-weak-md5.yaml +21 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B304-weak-des.yaml +20 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B307-use-of-eval.yaml +22 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B324-weak-sha1.yaml +21 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B501-no-cert-validation.yaml +23 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B602-shell-true.yaml +22 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B608-sqli.yaml +22 -0
- attocode_intel/rules/packs/community/eslint-typescript/LICENSE +21 -0
- attocode_intel/rules/packs/community/eslint-typescript/NOTICE +13 -0
- attocode_intel/rules/packs/community/eslint-typescript/PORTING.md +23 -0
- attocode_intel/rules/packs/community/eslint-typescript/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/eqeqeq.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-eval.yaml +22 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-implied-eval.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-new-func.yaml +21 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-throw-literal.yaml +19 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-var.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-with.yaml +18 -0
- attocode_intel/rules/packs/community/gosec-go/LICENSE +176 -0
- attocode_intel/rules/packs/community/gosec-go/NOTICE +13 -0
- attocode_intel/rules/packs/community/gosec-go/PORTING.md +23 -0
- attocode_intel/rules/packs/community/gosec-go/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G101-hardcoded-credentials.yaml +22 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G102-bind-all-interfaces.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G103-unsafe-block.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G201-sql-format.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G304-file-inclusion.yaml +23 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G401-weak-cipher.yaml +20 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G402-tls-min-version.yaml +20 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G501-md5-import.yaml +21 -0
- attocode_intel/rules/packs/examples/cpp/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/cpp/rules/security.yaml +90 -0
- attocode_intel/rules/packs/examples/cpp/rules/structural.yaml +42 -0
- attocode_intel/rules/packs/examples/csharp/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/csharp/rules/security.yaml +69 -0
- attocode_intel/rules/packs/examples/csharp/rules/structural.yaml +42 -0
- attocode_intel/rules/packs/examples/go/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/go/rules/performance.yaml +136 -0
- attocode_intel/rules/packs/examples/go/rules/security.yaml +48 -0
- attocode_intel/rules/packs/examples/go/rules/structural.yaml +58 -0
- attocode_intel/rules/packs/examples/go/taint/sanitizers.yaml +24 -0
- attocode_intel/rules/packs/examples/go/taint/sinks.yaml +51 -0
- attocode_intel/rules/packs/examples/go/taint/sources.yaml +26 -0
- attocode_intel/rules/packs/examples/java/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/java/rules/correctness.yaml +55 -0
- attocode_intel/rules/packs/examples/java/rules/structural.yaml +48 -0
- attocode_intel/rules/packs/examples/java/taint/sinks.yaml +37 -0
- attocode_intel/rules/packs/examples/java/taint/sources.yaml +21 -0
- attocode_intel/rules/packs/examples/kotlin/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/kotlin/rules/correctness.yaml +50 -0
- attocode_intel/rules/packs/examples/kotlin/rules/structural.yaml +38 -0
- attocode_intel/rules/packs/examples/php/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/php/rules/security.yaml +66 -0
- attocode_intel/rules/packs/examples/php/rules/structural.yaml +41 -0
- attocode_intel/rules/packs/examples/python/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/python/rules/performance.yaml +90 -0
- attocode_intel/rules/packs/examples/python/rules/structural.yaml +55 -0
- attocode_intel/rules/packs/examples/ruby/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/ruby/rules/security.yaml +73 -0
- attocode_intel/rules/packs/examples/ruby/rules/structural.yaml +41 -0
- attocode_intel/rules/packs/examples/rust/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/rust/rules/correctness.yaml +51 -0
- attocode_intel/rules/packs/examples/rust/rules/structural.yaml +49 -0
- attocode_intel/rules/packs/examples/typescript/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/typescript/rules/performance.yaml +70 -0
- attocode_intel/rules/packs/examples/typescript/rules/structural.yaml +48 -0
- attocode_intel/rules/packs/pack_loader.py +244 -0
- attocode_intel/rules/plugins/__init__.py +1 -0
- attocode_intel/rules/plugins/plugin_loader.py +132 -0
- attocode_intel/rules/profiling.py +332 -0
- attocode_intel/rules/registry.py +213 -0
- attocode_intel/rules/sarif.py +187 -0
- attocode_intel/rules/synthesis.py +420 -0
- attocode_intel/rules/testing.py +326 -0
- attocode_intel/server.py +734 -0
- attocode_intel/service.py +3416 -0
- attocode_intel/sharing.py +57 -0
- attocode_intel/storage/__init__.py +6 -0
- attocode_intel/storage/branch_overlay.py +352 -0
- attocode_intel/storage/content_store.py +220 -0
- attocode_intel/storage/dependency_store.py +143 -0
- attocode_intel/storage/diff_engine.py +187 -0
- attocode_intel/storage/embedding_store.py +594 -0
- attocode_intel/storage/provenance_store.py +137 -0
- attocode_intel/storage/security_scanner_db.py +164 -0
- attocode_intel/storage/symbol_store.py +160 -0
- attocode_intel/structural_evidence.py +144 -0
- attocode_intel/symbol_inspection.py +141 -0
- attocode_intel/symbol_links.py +167 -0
- attocode_intel/telemetry.py +42 -0
- attocode_intel/test_ranking.py +73 -0
- attocode_intel/testing/__init__.py +43 -0
- attocode_intel/testing/fixtures.py +122 -0
- attocode_intel/testing/helpers.py +75 -0
- attocode_intel/testing/mocks.py +99 -0
- attocode_intel/tools/__init__.py +2 -0
- attocode_intel/tools/adr_tools.py +628 -0
- attocode_intel/tools/analysis_tools.py +382 -0
- attocode_intel/tools/composite_tools.py +695 -0
- attocode_intel/tools/cross_mode_tools.py +173 -0
- attocode_intel/tools/dead_code_tools.py +582 -0
- attocode_intel/tools/distill_tools.py +353 -0
- attocode_intel/tools/frecency_tools.py +220 -0
- attocode_intel/tools/fuzzy_tools.py +255 -0
- attocode_intel/tools/history_tools.py +486 -0
- attocode_intel/tools/learning_tools.py +222 -0
- attocode_intel/tools/lsp_tools.py +218 -0
- attocode_intel/tools/maintenance_tools.py +1662 -0
- attocode_intel/tools/navigation_tools.py +220 -0
- attocode_intel/tools/overlay_tools.py +540 -0
- attocode_intel/tools/pin_store.py +432 -0
- attocode_intel/tools/pin_tools.py +266 -0
- attocode_intel/tools/query_constraints_tools.py +262 -0
- attocode_intel/tools/query_history_tools.py +158 -0
- attocode_intel/tools/readiness_tools.py +44 -0
- attocode_intel/tools/rule_tools.py +992 -0
- attocode_intel/tools/search_tools.py +770 -0
- attocode_intel/tools/snapshot_tools.py +624 -0
- attocode_intel/workers/__init__.py +1 -0
- attocode_intel/workers/job_utils.py +111 -0
- attocode_intel/workers/jobs.py +573 -0
- attocode_intel/workers/run.py +30 -0
- attocode_intel/workers/settings.py +61 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""Dependency auditing — check pinning and known vulnerabilities.
|
|
2
|
+
|
|
3
|
+
Parses common dependency files and flags:
|
|
4
|
+
- Unpinned/floating version specifiers (>= instead of ==)
|
|
5
|
+
- Known-vulnerable package ranges (via OSV.dev cache)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from attocode_intel._internal.integrations.security.patterns import Category, Severity
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# Cache directory for OSV vulnerability data
|
|
23
|
+
_DEFAULT_CACHE_DIR = os.path.expanduser("~/.attocode")
|
|
24
|
+
_OSV_CACHE_FILE = "osv-cache.json"
|
|
25
|
+
_OSV_CACHE_TTL = 86400 # 24 hours
|
|
26
|
+
|
|
27
|
+
# Suspicious tokens in package.json install hooks (GlassWorm-class supply-chain attacks)
|
|
28
|
+
_SUSPICIOUS_HOOK_TOKENS: list[tuple[str, "re.Pattern[str]", str]] = [
|
|
29
|
+
("hook_eval", re.compile(r"\beval\b"), "dynamic evaluation in install hook"),
|
|
30
|
+
("hook_atob", re.compile(r"\batob\s*\("), "atob decoder in install hook"),
|
|
31
|
+
("hook_buffer_b64", re.compile(r"Buffer\.from\s*\([^)]*base64", re.IGNORECASE),
|
|
32
|
+
"Buffer.from base64 decode in install hook"),
|
|
33
|
+
("hook_child_process", re.compile(r"require\s*\(\s*['\"]child_process['\"]\s*\)"),
|
|
34
|
+
"child_process require in install hook"),
|
|
35
|
+
("hook_node_dash_e", re.compile(r"\bnode\s+-e\b"),
|
|
36
|
+
"node -e inline script in install hook"),
|
|
37
|
+
("hook_curl_pipe_sh", re.compile(r"\b(?:curl|wget|fetch)\b[^|]*\|\s*(?:ba)?sh\b", re.IGNORECASE),
|
|
38
|
+
"curl/wget piped to shell in install hook"),
|
|
39
|
+
("hook_remote_script", re.compile(r"https?://\S+\.(?:sh|py|exe|dll|so|dylib)\b", re.IGNORECASE),
|
|
40
|
+
"remote script fetch in install hook"),
|
|
41
|
+
("hook_popen", re.compile(r"\bpopen\s*\("),
|
|
42
|
+
"popen call in install hook"),
|
|
43
|
+
("hook_system_call", re.compile(r"\bsystem\s*\("),
|
|
44
|
+
"system call in install hook"),
|
|
45
|
+
("hook_child_spawn", re.compile(r"\b(?:spawnSync|execSync|execFile|execFileSync)\s*\("),
|
|
46
|
+
"child_process spawn/exec variant in install hook"),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Suspicious patterns in setup.py (supply-chain attacks: ctx, W4SP, LiteLLM).
|
|
50
|
+
# setup.py should never make network calls; install-time fetches are a classic
|
|
51
|
+
# vector for staged-payload delivery.
|
|
52
|
+
_SUSPICIOUS_SETUP_PY_PATTERNS: list[tuple[str, "re.Pattern[str]", str]] = [
|
|
53
|
+
("setup_urllib_fetch",
|
|
54
|
+
re.compile(r"\burllib(?:\.request)?\.urlopen\s*\("),
|
|
55
|
+
"urllib network fetch"),
|
|
56
|
+
("setup_urlretrieve",
|
|
57
|
+
re.compile(r"\burlretrieve\s*\("),
|
|
58
|
+
"urlretrieve fetch"),
|
|
59
|
+
("setup_http_client_call",
|
|
60
|
+
re.compile(r"\b(?:requests|httpx|aiohttp)\.(?:get|post|put|delete|head)\s*\("),
|
|
61
|
+
"HTTP client call"),
|
|
62
|
+
("setup_socket_connect",
|
|
63
|
+
re.compile(r"\bsocket\.create_connection\s*\("),
|
|
64
|
+
"raw socket connection"),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(slots=True)
|
|
69
|
+
class DependencyFinding:
|
|
70
|
+
"""A finding from dependency auditing."""
|
|
71
|
+
|
|
72
|
+
package: str
|
|
73
|
+
version_spec: str
|
|
74
|
+
severity: Severity
|
|
75
|
+
category: Category = Category.DEPENDENCY
|
|
76
|
+
cwe_id: str = ""
|
|
77
|
+
message: str = ""
|
|
78
|
+
recommendation: str = ""
|
|
79
|
+
source_file: str = ""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(slots=True)
|
|
83
|
+
class DependencyAuditor:
|
|
84
|
+
"""Audit project dependencies for security issues."""
|
|
85
|
+
|
|
86
|
+
root_dir: str
|
|
87
|
+
_osv_cache: dict[str, Any] = field(default_factory=dict, repr=False)
|
|
88
|
+
_cache_loaded: bool = field(default=False, repr=False)
|
|
89
|
+
|
|
90
|
+
def audit(self) -> list[DependencyFinding]:
|
|
91
|
+
"""Run a full dependency audit.
|
|
92
|
+
|
|
93
|
+
Checks all supported dependency files for pinning issues.
|
|
94
|
+
"""
|
|
95
|
+
findings: list[DependencyFinding] = []
|
|
96
|
+
root = Path(self.root_dir)
|
|
97
|
+
|
|
98
|
+
# Python: pyproject.toml
|
|
99
|
+
pyproject = root / "pyproject.toml"
|
|
100
|
+
if pyproject.exists():
|
|
101
|
+
findings.extend(self._audit_pyproject(pyproject))
|
|
102
|
+
|
|
103
|
+
# Python: requirements.txt
|
|
104
|
+
reqs = root / "requirements.txt"
|
|
105
|
+
if reqs.exists():
|
|
106
|
+
findings.extend(self._audit_requirements(reqs))
|
|
107
|
+
|
|
108
|
+
# Node: package.json
|
|
109
|
+
pkg_json = root / "package.json"
|
|
110
|
+
if pkg_json.exists():
|
|
111
|
+
findings.extend(self._audit_package_json(pkg_json))
|
|
112
|
+
|
|
113
|
+
# Python: setup.py (supply-chain network-at-install-time check)
|
|
114
|
+
setup_py = root / "setup.py"
|
|
115
|
+
if setup_py.exists():
|
|
116
|
+
findings.extend(self._audit_setup_py(setup_py))
|
|
117
|
+
|
|
118
|
+
return findings
|
|
119
|
+
|
|
120
|
+
def _audit_pyproject(self, path: Path) -> list[DependencyFinding]:
|
|
121
|
+
"""Audit a pyproject.toml for dependency pinning."""
|
|
122
|
+
findings: list[DependencyFinding] = []
|
|
123
|
+
try:
|
|
124
|
+
import tomllib
|
|
125
|
+
with open(path, "rb") as f:
|
|
126
|
+
data = tomllib.load(f)
|
|
127
|
+
except (OSError, Exception):
|
|
128
|
+
return findings
|
|
129
|
+
|
|
130
|
+
# PEP 621 dependencies
|
|
131
|
+
deps = data.get("project", {}).get("dependencies", [])
|
|
132
|
+
# Also check optional-dependencies
|
|
133
|
+
opt_deps = data.get("project", {}).get("optional-dependencies", {})
|
|
134
|
+
for group_deps in opt_deps.values():
|
|
135
|
+
if isinstance(group_deps, list):
|
|
136
|
+
deps.extend(group_deps)
|
|
137
|
+
|
|
138
|
+
for dep_spec in deps:
|
|
139
|
+
if not isinstance(dep_spec, str):
|
|
140
|
+
continue
|
|
141
|
+
dep = dep_spec.strip()
|
|
142
|
+
if not dep or dep.startswith("#"):
|
|
143
|
+
continue
|
|
144
|
+
finding = self._check_pinning(dep, str(path))
|
|
145
|
+
if finding:
|
|
146
|
+
findings.append(finding)
|
|
147
|
+
|
|
148
|
+
return findings
|
|
149
|
+
|
|
150
|
+
def _audit_requirements(self, path: Path) -> list[DependencyFinding]:
|
|
151
|
+
"""Audit a requirements.txt for dependency pinning."""
|
|
152
|
+
findings: list[DependencyFinding] = []
|
|
153
|
+
try:
|
|
154
|
+
content = path.read_text(encoding="utf-8")
|
|
155
|
+
except OSError:
|
|
156
|
+
return findings
|
|
157
|
+
|
|
158
|
+
for line in content.split("\n"):
|
|
159
|
+
line = line.strip()
|
|
160
|
+
if not line or line.startswith("#") or line.startswith("-"):
|
|
161
|
+
continue
|
|
162
|
+
finding = self._check_pinning(line, str(path))
|
|
163
|
+
if finding:
|
|
164
|
+
findings.append(finding)
|
|
165
|
+
|
|
166
|
+
return findings
|
|
167
|
+
|
|
168
|
+
def _audit_package_json(self, path: Path) -> list[DependencyFinding]:
|
|
169
|
+
"""Audit a package.json for dependency pinning."""
|
|
170
|
+
findings: list[DependencyFinding] = []
|
|
171
|
+
try:
|
|
172
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
173
|
+
except (OSError, json.JSONDecodeError):
|
|
174
|
+
return findings
|
|
175
|
+
|
|
176
|
+
for section in ("dependencies", "devDependencies"):
|
|
177
|
+
deps = data.get(section, {})
|
|
178
|
+
if not isinstance(deps, dict):
|
|
179
|
+
continue
|
|
180
|
+
for pkg, version in deps.items():
|
|
181
|
+
if not isinstance(version, str):
|
|
182
|
+
continue
|
|
183
|
+
# Flag loose version ranges
|
|
184
|
+
if version.startswith("*") or version == "latest":
|
|
185
|
+
findings.append(DependencyFinding(
|
|
186
|
+
package=pkg,
|
|
187
|
+
version_spec=version,
|
|
188
|
+
severity=Severity.HIGH,
|
|
189
|
+
message=f"Unpinned dependency: {pkg}@{version}",
|
|
190
|
+
recommendation="Pin to a specific version range",
|
|
191
|
+
source_file=str(path),
|
|
192
|
+
))
|
|
193
|
+
elif version.startswith(">=") and "<" not in version:
|
|
194
|
+
findings.append(DependencyFinding(
|
|
195
|
+
package=pkg,
|
|
196
|
+
version_spec=version,
|
|
197
|
+
severity=Severity.LOW,
|
|
198
|
+
message=f"Open-ended version range: {pkg}@{version}",
|
|
199
|
+
recommendation="Consider adding upper bound (e.g. >=1.0,<2.0)",
|
|
200
|
+
source_file=str(path),
|
|
201
|
+
))
|
|
202
|
+
|
|
203
|
+
findings.extend(self._audit_install_hooks(data, path))
|
|
204
|
+
return findings
|
|
205
|
+
|
|
206
|
+
def _audit_install_hooks(self, data: dict, path: Path) -> list[DependencyFinding]:
|
|
207
|
+
"""Flag package.json install hooks with obfuscation/remote-fetch patterns.
|
|
208
|
+
|
|
209
|
+
Supply-chain attackers (GlassWorm-class) place obfuscated one-liners in
|
|
210
|
+
preinstall/postinstall/install scripts that execute automatically on
|
|
211
|
+
``npm install``. This detects the most common indicators.
|
|
212
|
+
|
|
213
|
+
Note: only the root-level package.json is audited (same as pinning check).
|
|
214
|
+
Monorepo workspace package.json files are not traversed.
|
|
215
|
+
"""
|
|
216
|
+
findings: list[DependencyFinding] = []
|
|
217
|
+
scripts = data.get("scripts", {})
|
|
218
|
+
if not isinstance(scripts, dict):
|
|
219
|
+
return findings
|
|
220
|
+
for hook in ("preinstall", "install", "postinstall"):
|
|
221
|
+
script = scripts.get(hook)
|
|
222
|
+
if not isinstance(script, str) or not script.strip():
|
|
223
|
+
continue
|
|
224
|
+
for _name, regex, description in _SUSPICIOUS_HOOK_TOKENS:
|
|
225
|
+
if regex.search(script):
|
|
226
|
+
findings.append(DependencyFinding(
|
|
227
|
+
package=hook,
|
|
228
|
+
version_spec=script[:200],
|
|
229
|
+
severity=Severity.HIGH,
|
|
230
|
+
cwe_id="CWE-506",
|
|
231
|
+
message=f"Suspicious {hook} script: {description}",
|
|
232
|
+
recommendation=(
|
|
233
|
+
f"Review the '{hook}' script in package.json; "
|
|
234
|
+
"supply-chain attackers use install hooks to run "
|
|
235
|
+
"obfuscated one-liners on `npm install`"
|
|
236
|
+
),
|
|
237
|
+
source_file=str(path),
|
|
238
|
+
))
|
|
239
|
+
break # one finding per hook is enough
|
|
240
|
+
return findings
|
|
241
|
+
|
|
242
|
+
def _audit_setup_py(self, path: Path) -> list[DependencyFinding]:
|
|
243
|
+
"""Flag network calls in setup.py (supply-chain attack vector).
|
|
244
|
+
|
|
245
|
+
setup.py executes arbitrary Python at install time. Legitimate setup
|
|
246
|
+
scripts only define metadata and dependencies; network fetches there
|
|
247
|
+
are almost always a staged-payload delivery mechanism (ctx/W4SP/
|
|
248
|
+
LiteLLM style attacks).
|
|
249
|
+
"""
|
|
250
|
+
findings: list[DependencyFinding] = []
|
|
251
|
+
try:
|
|
252
|
+
content = path.read_text(encoding="utf-8")
|
|
253
|
+
except OSError:
|
|
254
|
+
return findings
|
|
255
|
+
for name, regex, description in _SUSPICIOUS_SETUP_PY_PATTERNS:
|
|
256
|
+
if regex.search(content):
|
|
257
|
+
findings.append(DependencyFinding(
|
|
258
|
+
package="setup.py",
|
|
259
|
+
version_spec=name,
|
|
260
|
+
severity=Severity.HIGH,
|
|
261
|
+
cwe_id="CWE-506",
|
|
262
|
+
message=f"setup.py contains {description} — supply-chain attack pattern",
|
|
263
|
+
recommendation=(
|
|
264
|
+
"Setup scripts should not make network calls; "
|
|
265
|
+
"install-time fetches are a classic supply-chain "
|
|
266
|
+
"vector (cf. PyPI ctx, W4SP stealer)"
|
|
267
|
+
),
|
|
268
|
+
source_file=str(path),
|
|
269
|
+
))
|
|
270
|
+
return findings
|
|
271
|
+
|
|
272
|
+
def _check_pinning(self, dep_spec: str, source_file: str) -> DependencyFinding | None:
|
|
273
|
+
"""Check a single dependency specifier for pinning issues."""
|
|
274
|
+
# Parse package name and version spec
|
|
275
|
+
match = re.match(r"^([a-zA-Z0-9_.-]+)\s*(.*)$", dep_spec)
|
|
276
|
+
if not match:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
pkg = match.group(1)
|
|
280
|
+
version = match.group(2).strip()
|
|
281
|
+
|
|
282
|
+
if not version:
|
|
283
|
+
return DependencyFinding(
|
|
284
|
+
package=pkg,
|
|
285
|
+
version_spec="(none)",
|
|
286
|
+
severity=Severity.MEDIUM,
|
|
287
|
+
message=f"No version constraint for {pkg}",
|
|
288
|
+
recommendation=f"Add version constraint (e.g. {pkg}>=1.0)",
|
|
289
|
+
source_file=source_file,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Floating >= without upper bound
|
|
293
|
+
if ">=" in version and "<" not in version and "==" not in version:
|
|
294
|
+
return DependencyFinding(
|
|
295
|
+
package=pkg,
|
|
296
|
+
version_spec=version,
|
|
297
|
+
severity=Severity.LOW,
|
|
298
|
+
message=f"Open-ended version: {pkg}{version}",
|
|
299
|
+
recommendation="Consider adding upper bound for reproducibility",
|
|
300
|
+
source_file=source_file,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return None
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Shared pattern-matching iteration for security scanners.
|
|
2
|
+
|
|
3
|
+
Both the filesystem-based scanner (`scanner.py`) and the DB-backed scanner
|
|
4
|
+
(`security_scanner_db.py`) need to walk lines of a file, apply per-pattern
|
|
5
|
+
language filtering, skip comment lines (unless a pattern opts in via
|
|
6
|
+
``scan_comments``), and yield regex hits. This module centralises that logic.
|
|
7
|
+
|
|
8
|
+
Callers construct their own finding record types — this generator only
|
|
9
|
+
reports (line_number, line_text, matched_pattern).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Iterable, Iterator
|
|
15
|
+
|
|
16
|
+
from attocode_intel._internal.integrations.security.patterns import SecurityPattern
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def iter_pattern_matches(
|
|
20
|
+
content: str,
|
|
21
|
+
patterns: Iterable[SecurityPattern],
|
|
22
|
+
language: str,
|
|
23
|
+
) -> Iterator[tuple[int, str, SecurityPattern]]:
|
|
24
|
+
"""Yield (line_number, line, pattern) for every pattern hit in ``content``.
|
|
25
|
+
|
|
26
|
+
Filters applied per line × pattern:
|
|
27
|
+
- Language filter: skip patterns scoped to other languages.
|
|
28
|
+
- Comment-line skip: lines starting with ``#`` or ``//`` are skipped
|
|
29
|
+
unless the pattern sets ``scan_comments=True`` (e.g. for stego payload
|
|
30
|
+
detectors that specifically want to scan comments).
|
|
31
|
+
|
|
32
|
+
A line may yield multiple matches if it hits multiple patterns; each
|
|
33
|
+
produces a separate tuple.
|
|
34
|
+
"""
|
|
35
|
+
for line_no, line in enumerate(content.splitlines(), 1):
|
|
36
|
+
stripped = line.lstrip()
|
|
37
|
+
is_comment = stripped.startswith("#") or stripped.startswith("//")
|
|
38
|
+
for pat in patterns:
|
|
39
|
+
if pat.languages and language not in pat.languages:
|
|
40
|
+
continue
|
|
41
|
+
if is_comment and not pat.scan_comments:
|
|
42
|
+
continue
|
|
43
|
+
if pat.pattern.search(line):
|
|
44
|
+
yield line_no, line, pat
|