aictem 0.1.0__tar.gz

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.
@@ -0,0 +1,25 @@
1
+ # local infrastructure and secrets
2
+ .local/
3
+ .env
4
+ .env.*
5
+ !.env.example
6
+ # python
7
+ __pycache__/
8
+ *.pyc
9
+ .venv/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ htmlcov/
15
+ backend/var/
16
+ # node
17
+ node_modules/
18
+ .next/
19
+ out/
20
+ *.tsbuildinfo
21
+ coverage/
22
+ # os / editors
23
+ .DS_Store
24
+ .idea/
25
+ .vscode/
aictem-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 d0gra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
aictem-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.5
2
+ Name: aictem
3
+ Version: 0.1.0
4
+ Summary: Discover repository, dependency, container, cloud and AI assets (providers, agents, RAG, MCP, models) from inside a repository
5
+ Project-URL: Homepage, https://github.com/d0gra/AI-CTEM
6
+ Project-URL: Documentation, https://github.com/d0gra/AI-CTEM/blob/main/cli/README.md
7
+ Project-URL: Repository, https://github.com/d0gra/AI-CTEM
8
+ Project-URL: Issues, https://github.com/d0gra/AI-CTEM/issues
9
+ Project-URL: Changelog, https://github.com/d0gra/AI-CTEM/blob/main/CHANGELOG.md
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai-security,asset-discovery,ctem,llm,mcp,sarif,sbom,security,shadow-ai
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Information Technology
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Security
25
+ Classifier: Topic :: Software Development :: Quality Assurance
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.11
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # aictem: repository and AI asset discovery
33
+
34
+ `aictem` inventories a repository from the inside and reports what it finds as assets, relationships and findings. It covers dependencies, container images, cloud resources, CI pipelines, committed secrets, and AI systems (model providers, frameworks, agents and their tools, RAG pipelines, vector stores, MCP servers and clients, and model files).
35
+
36
+ - **Works offline, anywhere.** The standard library is the only dependency, so it runs in any CI image with Python 3.11 or later.
37
+ - **Safe by design.** Model files are disassembled, never loaded. Secret values are never stored or printed; findings carry a prefix, the length and a fingerprint.
38
+ - **Accurate.** Agent, tool, import and risky-flag patterns only count when they appear in real code, not in strings or comments. AI usage seen only in tests is labelled as such.
39
+ - **Connects to the platform.** `--upload` sends the inventory to [CTEM Platform](https://github.com/d0gra/AI-CTEM) with an upload-only pipeline token. Results merge with the platform's own analysis, because identifiers and rule names match.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pipx install aictem # or: uv tool install aictem
45
+ pip install aictem # into the current environment
46
+ uvx aictem discover . # or run it without installing
47
+ ```
48
+
49
+ From a checkout of this repository: `pipx install ./cli`.
50
+
51
+ ## Use
52
+
53
+ ```bash
54
+ aictem discover # scan the current directory; terminal summary
55
+ aictem discover ~/code/app --all # list every asset and finding
56
+ aictem discover . -f json -o inventory.json # full report (also the upload format)
57
+ aictem discover . -f sarif -o aictem.sarif # for GitHub code scanning and other SARIF dashboards
58
+ aictem discover . --approved-providers openai,anthropic # other providers are reported as shadow AI
59
+ aictem discover . --vulns # look up known vulnerabilities in pinned packages (osv.dev)
60
+ aictem discover . --fail-on high # exit 1 if a high or critical finding is present
61
+ ```
62
+
63
+ Send the inventory to the platform. Create a token under **Integrations → CI/CD pipelines**:
64
+
65
+ ```bash
66
+ export CTEM_URL=https://ctem.example.com CTEM_CI_TOKEN=ctem_ci_...
67
+ aictem discover . --upload --gate high # exit 3 if the platform's gate fails
68
+ ```
69
+
70
+ Inside GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, CircleCI or Bitbucket, the upload records the provider and the pipeline URL automatically, together with the repository, branch and commit from git.
71
+
72
+ ## What it finds
73
+
74
+ | Area | Sources | Assets | Findings (examples) |
75
+ | --- | --- | --- | --- |
76
+ | Dependencies | requirements, pyproject, Pipfile, poetry/uv locks, package.json, npm/pnpm/yarn locks, go.mod, Cargo, Gemfile.lock, composer, pom.xml, Gradle, .csproj | `package` (purl), `repository` | Known vulnerabilities with `--vulns`, or by the platform on upload |
77
+ | AI providers and serving | Imports, dependencies, endpoint URLs, environment variables, containers, IaC | `ai_provider`, `llm_endpoint` | Unapproved provider (shadow AI) |
78
+ | AI frameworks and agents | LangChain/LangGraph, LlamaIndex, Haystack, CrewAI, AutoGen, OpenAI Agents SDK, Pydantic AI, smolagents, Semantic Kernel, Google ADK, Strands, Vercel AI SDK, Mastra, function-calling tool schemas | `ai_framework`, `ai_agent` (with tools and capabilities) | High-impact tools without an approval step, disabled LangChain safety flags, model output passed to exec/eval |
79
+ | Retrieval | Vector store clients plus retrievers, loaders, splitters and embeddings | `rag_application`, `vector_database` | |
80
+ | MCP | Client configs (Claude Desktop, Cursor, VS Code, Windsurf, Claude Code and more) and servers implemented in the repository | `mcp_client`, `mcp_server` | Plaintext transport, literal credentials, unpinned packages, broad filesystem roots, privileged launch, auto-approved tools, tool-description poisoning |
81
+ | Models | Pickle, PyTorch, safetensors, ONNX, GGUF, Keras and more; Hugging Face model references | `ai_model` | Code-execution callables in pickle streams, unsafe formats, `trust_remote_code=True`, `torch.load` without `weights_only` |
82
+ | Containers | Dockerfile, Compose, Kubernetes manifests | `container_image`, `k8s_resource` | Root user, unpinned base image, privileged containers |
83
+ | Cloud (IaC) | Terraform, CloudFormation, including SageMaker, Bedrock, Vertex AI and Azure OpenAI | `storage`, `database`, `identity`, `llm_endpoint`, ... | Public buckets, publicly accessible databases |
84
+ | CI/CD | GitHub Actions and other pipeline files | (attributes) | Unpinned third-party actions, pull_request_target running PR code, write-all tokens |
85
+ | Secrets | AI provider keys, cloud and SaaS tokens, private keys | | Committed credential (value never stored) |
86
+
87
+ Exclude paths with `--exclude GLOB` or a `.aictemignore` file (one glob per line). Dependency folders such as `node_modules`, `.venv`, `dist` and `build` are always skipped.
88
+
89
+ ## Exit codes
90
+
91
+ | Code | Meaning |
92
+ | --- | --- |
93
+ | 0 | Success |
94
+ | 1 | `--fail-on` threshold reached |
95
+ | 2 | Usage or upload error |
96
+ | 3 | Upload succeeded, but the platform's `--gate` failed |
97
+
98
+ ## Development
99
+
100
+ ```bash
101
+ cd cli && uv run --with pytest pytest
102
+ ```
103
+
104
+ MIT licensed.
105
+
106
+ ## Releasing
107
+
108
+ Versions follow the project's `CHANGELOG.md`. A release is a tag: GitHub Actions runs the tests on
109
+ Python 3.11-3.13, builds the sdist and wheel, installs the built wheel and runs it, then publishes to
110
+ PyPI and opens a GitHub release.
111
+
112
+ ```bash
113
+ # 1. set the new version in cli/pyproject.toml and note the changes in CHANGELOG.md, then commit
114
+ # 2. tag it; the tag must match the version, or the workflow stops before publishing
115
+ git tag cli-v0.2.0 && git push origin cli-v0.2.0
116
+ ```
117
+
118
+ `workflow_dispatch` runs the same workflow without publishing, for checking a release before tagging.
119
+
120
+ ### One-time PyPI setup
121
+
122
+ Publishing uses [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no API token is
123
+ stored in the repository: GitHub proves the workflow's identity over OpenID Connect and PyPI issues a
124
+ short-lived token for that one upload. Before the first release, add the publisher on PyPI
125
+ (**Your projects -> aictem -> Publishing**, or **Publishing** under your account for a project that does
126
+ not exist yet):
127
+
128
+ | Field | Value |
129
+ | --- | --- |
130
+ | Owner | `d0gra` |
131
+ | Repository name | `AI-CTEM` |
132
+ | Workflow name | `release-cli.yml` |
133
+ | Environment name | `pypi` |
134
+
135
+ The environment is created by the first run, so nothing is needed on the GitHub side. To require an
136
+ approval before each upload, add a protection rule to that environment in the repository settings.
aictem-0.1.0/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # aictem: repository and AI asset discovery
2
+
3
+ `aictem` inventories a repository from the inside and reports what it finds as assets, relationships and findings. It covers dependencies, container images, cloud resources, CI pipelines, committed secrets, and AI systems (model providers, frameworks, agents and their tools, RAG pipelines, vector stores, MCP servers and clients, and model files).
4
+
5
+ - **Works offline, anywhere.** The standard library is the only dependency, so it runs in any CI image with Python 3.11 or later.
6
+ - **Safe by design.** Model files are disassembled, never loaded. Secret values are never stored or printed; findings carry a prefix, the length and a fingerprint.
7
+ - **Accurate.** Agent, tool, import and risky-flag patterns only count when they appear in real code, not in strings or comments. AI usage seen only in tests is labelled as such.
8
+ - **Connects to the platform.** `--upload` sends the inventory to [CTEM Platform](https://github.com/d0gra/AI-CTEM) with an upload-only pipeline token. Results merge with the platform's own analysis, because identifiers and rule names match.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pipx install aictem # or: uv tool install aictem
14
+ pip install aictem # into the current environment
15
+ uvx aictem discover . # or run it without installing
16
+ ```
17
+
18
+ From a checkout of this repository: `pipx install ./cli`.
19
+
20
+ ## Use
21
+
22
+ ```bash
23
+ aictem discover # scan the current directory; terminal summary
24
+ aictem discover ~/code/app --all # list every asset and finding
25
+ aictem discover . -f json -o inventory.json # full report (also the upload format)
26
+ aictem discover . -f sarif -o aictem.sarif # for GitHub code scanning and other SARIF dashboards
27
+ aictem discover . --approved-providers openai,anthropic # other providers are reported as shadow AI
28
+ aictem discover . --vulns # look up known vulnerabilities in pinned packages (osv.dev)
29
+ aictem discover . --fail-on high # exit 1 if a high or critical finding is present
30
+ ```
31
+
32
+ Send the inventory to the platform. Create a token under **Integrations → CI/CD pipelines**:
33
+
34
+ ```bash
35
+ export CTEM_URL=https://ctem.example.com CTEM_CI_TOKEN=ctem_ci_...
36
+ aictem discover . --upload --gate high # exit 3 if the platform's gate fails
37
+ ```
38
+
39
+ Inside GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, CircleCI or Bitbucket, the upload records the provider and the pipeline URL automatically, together with the repository, branch and commit from git.
40
+
41
+ ## What it finds
42
+
43
+ | Area | Sources | Assets | Findings (examples) |
44
+ | --- | --- | --- | --- |
45
+ | Dependencies | requirements, pyproject, Pipfile, poetry/uv locks, package.json, npm/pnpm/yarn locks, go.mod, Cargo, Gemfile.lock, composer, pom.xml, Gradle, .csproj | `package` (purl), `repository` | Known vulnerabilities with `--vulns`, or by the platform on upload |
46
+ | AI providers and serving | Imports, dependencies, endpoint URLs, environment variables, containers, IaC | `ai_provider`, `llm_endpoint` | Unapproved provider (shadow AI) |
47
+ | AI frameworks and agents | LangChain/LangGraph, LlamaIndex, Haystack, CrewAI, AutoGen, OpenAI Agents SDK, Pydantic AI, smolagents, Semantic Kernel, Google ADK, Strands, Vercel AI SDK, Mastra, function-calling tool schemas | `ai_framework`, `ai_agent` (with tools and capabilities) | High-impact tools without an approval step, disabled LangChain safety flags, model output passed to exec/eval |
48
+ | Retrieval | Vector store clients plus retrievers, loaders, splitters and embeddings | `rag_application`, `vector_database` | |
49
+ | MCP | Client configs (Claude Desktop, Cursor, VS Code, Windsurf, Claude Code and more) and servers implemented in the repository | `mcp_client`, `mcp_server` | Plaintext transport, literal credentials, unpinned packages, broad filesystem roots, privileged launch, auto-approved tools, tool-description poisoning |
50
+ | Models | Pickle, PyTorch, safetensors, ONNX, GGUF, Keras and more; Hugging Face model references | `ai_model` | Code-execution callables in pickle streams, unsafe formats, `trust_remote_code=True`, `torch.load` without `weights_only` |
51
+ | Containers | Dockerfile, Compose, Kubernetes manifests | `container_image`, `k8s_resource` | Root user, unpinned base image, privileged containers |
52
+ | Cloud (IaC) | Terraform, CloudFormation, including SageMaker, Bedrock, Vertex AI and Azure OpenAI | `storage`, `database`, `identity`, `llm_endpoint`, ... | Public buckets, publicly accessible databases |
53
+ | CI/CD | GitHub Actions and other pipeline files | (attributes) | Unpinned third-party actions, pull_request_target running PR code, write-all tokens |
54
+ | Secrets | AI provider keys, cloud and SaaS tokens, private keys | | Committed credential (value never stored) |
55
+
56
+ Exclude paths with `--exclude GLOB` or a `.aictemignore` file (one glob per line). Dependency folders such as `node_modules`, `.venv`, `dist` and `build` are always skipped.
57
+
58
+ ## Exit codes
59
+
60
+ | Code | Meaning |
61
+ | --- | --- |
62
+ | 0 | Success |
63
+ | 1 | `--fail-on` threshold reached |
64
+ | 2 | Usage or upload error |
65
+ | 3 | Upload succeeded, but the platform's `--gate` failed |
66
+
67
+ ## Development
68
+
69
+ ```bash
70
+ cd cli && uv run --with pytest pytest
71
+ ```
72
+
73
+ MIT licensed.
74
+
75
+ ## Releasing
76
+
77
+ Versions follow the project's `CHANGELOG.md`. A release is a tag: GitHub Actions runs the tests on
78
+ Python 3.11-3.13, builds the sdist and wheel, installs the built wheel and runs it, then publishes to
79
+ PyPI and opens a GitHub release.
80
+
81
+ ```bash
82
+ # 1. set the new version in cli/pyproject.toml and note the changes in CHANGELOG.md, then commit
83
+ # 2. tag it; the tag must match the version, or the workflow stops before publishing
84
+ git tag cli-v0.2.0 && git push origin cli-v0.2.0
85
+ ```
86
+
87
+ `workflow_dispatch` runs the same workflow without publishing, for checking a release before tagging.
88
+
89
+ ### One-time PyPI setup
90
+
91
+ Publishing uses [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no API token is
92
+ stored in the repository: GitHub proves the workflow's identity over OpenID Connect and PyPI issues a
93
+ short-lived token for that one upload. Before the first release, add the publisher on PyPI
94
+ (**Your projects -> aictem -> Publishing**, or **Publishing** under your account for a project that does
95
+ not exist yet):
96
+
97
+ | Field | Value |
98
+ | --- | --- |
99
+ | Owner | `d0gra` |
100
+ | Repository name | `AI-CTEM` |
101
+ | Workflow name | `release-cli.yml` |
102
+ | Environment name | `pypi` |
103
+
104
+ The environment is created by the first run, so nothing is needed on the GitHub side. To require an
105
+ approval before each upload, add a protection rule to that environment in the repository settings.
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "aictem"
3
+ version = "0.1.0"
4
+ description = "Discover repository, dependency, container, cloud and AI assets (providers, agents, RAG, MCP, models) from inside a repository"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ dependencies = [] # standard library only, so it runs in any CI image
10
+ keywords = ["security", "ctem", "ai-security", "sbom", "mcp", "llm", "agents", "shadow-ai", "asset-discovery", "sarif"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Intended Audience :: Information Technology",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Security",
24
+ "Topic :: Software Development :: Quality Assurance",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/d0gra/AI-CTEM"
30
+ Documentation = "https://github.com/d0gra/AI-CTEM/blob/main/cli/README.md"
31
+ Repository = "https://github.com/d0gra/AI-CTEM"
32
+ Issues = "https://github.com/d0gra/AI-CTEM/issues"
33
+ Changelog = "https://github.com/d0gra/AI-CTEM/blob/main/CHANGELOG.md"
34
+
35
+ [project.scripts]
36
+ aictem = "aictem.__main__:main"
37
+
38
+ [project.optional-dependencies]
39
+ dev = ["pytest>=8"]
40
+
41
+ [build-system]
42
+ requires = ["hatchling>=1.25"]
43
+ build-backend = "hatchling.build"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/aictem"]
47
+
48
+ [tool.hatch.build.targets.sdist]
49
+ include = ["src/", "tests/", "README.md", "LICENSE", "pyproject.toml"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ pythonpath = ["src"]
54
+
55
+ [tool.ruff]
56
+ line-length = 140
57
+ target-version = "py311"
@@ -0,0 +1,3 @@
1
+ """aictem: discover repository, dependency, container, cloud and AI assets."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,106 @@
1
+ """aictem command line.
2
+
3
+ aictem discover [PATH] inventory a repository: packages, containers, cloud, CI and AI assets, with findings
4
+ aictem discover . --format json -o r.json
5
+ aictem discover . --upload send the inventory to the CTEM platform (CTEM_URL, CTEM_CI_TOKEN)
6
+ aictem discover . --vulns also look up known vulnerabilities in pinned packages (OSV)
7
+ aictem discover . --fail-on high exit 1 if a finding at or above the severity is present
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import os
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ from aictem import __version__, output, scan
18
+ from aictem.model import SEVERITY_RANK
19
+
20
+ EXIT_FINDINGS, EXIT_UPLOAD_GATE, EXIT_ERROR = 1, 3, 2
21
+
22
+
23
+ def _parser() -> argparse.ArgumentParser:
24
+ parser = argparse.ArgumentParser(prog="aictem", description="Discover repository and AI assets and send them to the CTEM platform.")
25
+ parser.add_argument("--version", action="version", version=f"aictem {__version__}")
26
+ sub = parser.add_subparsers(dest="command", required=True)
27
+ d = sub.add_parser("discover", help="inventory a directory tree", description="Inventory a repository: dependencies, container images, cloud "
28
+ "resources, CI pipelines, secrets, and AI assets (providers, frameworks, agents, RAG, MCP servers, models).")
29
+ d.add_argument("path", nargs="?", default=".", help="directory to scan (default: current directory)")
30
+ d.add_argument("-f", "--format", choices=("table", "json", "sarif"), default="table", help="output format (json is the upload format)")
31
+ d.add_argument("-o", "--output", help="write the report to a file instead of standard output")
32
+ d.add_argument("--approved-providers", help="comma-separated AI provider keys; others are reported as shadow AI (for example openai,anthropic)")
33
+ d.add_argument("--no-secrets", action="store_true", help="skip the committed-credential scan")
34
+ d.add_argument("--vulns", action="store_true", help="look up known vulnerabilities in pinned packages at osv.dev (sends names and versions)")
35
+ d.add_argument("--exclude", action="append", default=[], metavar="GLOB", help="skip paths matching a glob (repeatable; also read from .aictemignore)")
36
+ d.add_argument("--max-files", type=int, default=50_000, help="stop after this many files (default 50000)")
37
+ d.add_argument("--fail-on", choices=("none", "low", "medium", "high", "critical"), default="none",
38
+ help="exit with status 1 when a finding at or above this severity is present")
39
+ d.add_argument("--all", action="store_true", help="table format: list every asset and finding")
40
+ up = d.add_argument_group("upload to the CTEM platform")
41
+ up.add_argument("--upload", action="store_true", help="send the report to the platform's CI ingestion endpoint")
42
+ up.add_argument("--url", default=os.environ.get("CTEM_URL"), help="platform address (default: $CTEM_URL)")
43
+ up.add_argument("--token-env", default="CTEM_CI_TOKEN", help="environment variable holding the pipeline token (default: CTEM_CI_TOKEN)")
44
+ up.add_argument("--gate", choices=("none", "low", "medium", "high", "critical"), default="none",
45
+ help="server-side gate: fail (exit 3) when open findings at or above this severity remain after the upload")
46
+ return parser
47
+
48
+
49
+ def main(argv: list[str] | None = None) -> int:
50
+ args = _parser().parse_args(argv)
51
+ approved = {p.strip().lower() for p in (args.approved_providers or "").split(",") if p.strip()} or None
52
+ report = scan.run(Path(args.path), approved_providers=approved, secrets_enabled=not args.no_secrets, max_files=args.max_files,
53
+ excludes=args.exclude)
54
+ if args.vulns:
55
+ from aictem import osv
56
+
57
+ try:
58
+ added = osv.enrich(report)
59
+ print(f"aictem: {added} known vulnerabilities in {report['summary']['vulnerabilities_checked']} pinned packages", file=sys.stderr)
60
+ except OSError as exc:
61
+ print(f"aictem: vulnerability lookup failed: {exc}", file=sys.stderr)
62
+
63
+ if args.format == "table" and not args.output:
64
+ output.table(report, show_all=args.all)
65
+ else:
66
+ rendered = output.to_sarif(report) if args.format == "sarif" else output.to_json(report) if args.format == "json" else None
67
+ if rendered is None: # table to a file
68
+ with open(args.output, "w", encoding="utf-8") as fh:
69
+ output.table(report, fh, show_all=True)
70
+ elif args.output:
71
+ Path(args.output).write_text(rendered, encoding="utf-8")
72
+ else:
73
+ sys.stdout.write(rendered)
74
+ if args.output:
75
+ s = report["summary"]
76
+ print(f"aictem: wrote {args.output}: {len(report['assets'])} assets ({s['ai_assets']} AI), {len(report['findings'])} findings", file=sys.stderr)
77
+
78
+ status = 0
79
+ if args.upload:
80
+ from aictem import upload
81
+
82
+ token = os.environ.get(args.token_env)
83
+ if not args.url or not token:
84
+ print(f"aictem: --upload needs --url (or CTEM_URL) and a pipeline token in ${args.token_env}", file=sys.stderr)
85
+ return EXIT_ERROR
86
+ code, body = upload.send(report, args.url, token, fail_on=args.gate)
87
+ if code >= 400 and code != 412:
88
+ print(f"aictem: upload failed (HTTP {code}): {(body.get('error') or {}).get('message') or body}", file=sys.stderr)
89
+ return EXIT_ERROR
90
+ gate = body.get("gate") or {}
91
+ print(f"aictem: uploaded ({body.get('status')}): {body.get('findings_new', 0)} new, {body.get('findings_seen', 0)} seen findings"
92
+ + (f"; gate {'passed' if gate.get('passed') else 'FAILED'} at {gate.get('threshold')}" if gate and gate.get("threshold") != "none" else ""),
93
+ file=sys.stderr)
94
+ if code == 412:
95
+ status = EXIT_UPLOAD_GATE
96
+
97
+ if args.fail_on != "none":
98
+ worst = output.worst(report)
99
+ if worst and SEVERITY_RANK[worst] >= SEVERITY_RANK[args.fail_on]:
100
+ print(f"aictem: findings at or above {args.fail_on} present (worst: {worst})", file=sys.stderr)
101
+ status = status or EXIT_FINDINGS
102
+ return status
103
+
104
+
105
+ if __name__ == "__main__":
106
+ sys.exit(main())