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.
- aictem-0.1.0/.gitignore +25 -0
- aictem-0.1.0/LICENSE +21 -0
- aictem-0.1.0/PKG-INFO +136 -0
- aictem-0.1.0/README.md +105 -0
- aictem-0.1.0/pyproject.toml +57 -0
- aictem-0.1.0/src/aictem/__init__.py +3 -0
- aictem-0.1.0/src/aictem/__main__.py +106 -0
- aictem-0.1.0/src/aictem/catalog.py +313 -0
- aictem-0.1.0/src/aictem/detectors/__init__.py +50 -0
- aictem-0.1.0/src/aictem/detectors/code.py +528 -0
- aictem-0.1.0/src/aictem/detectors/deps.py +334 -0
- aictem-0.1.0/src/aictem/detectors/infra.py +206 -0
- aictem-0.1.0/src/aictem/detectors/mcp.py +189 -0
- aictem-0.1.0/src/aictem/detectors/models.py +122 -0
- aictem-0.1.0/src/aictem/detectors/secrets.py +49 -0
- aictem-0.1.0/src/aictem/detectors/synth.py +155 -0
- aictem-0.1.0/src/aictem/gitinfo.py +102 -0
- aictem-0.1.0/src/aictem/model.py +139 -0
- aictem-0.1.0/src/aictem/osv.py +68 -0
- aictem-0.1.0/src/aictem/output.py +124 -0
- aictem-0.1.0/src/aictem/scan.py +100 -0
- aictem-0.1.0/src/aictem/upload.py +48 -0
- aictem-0.1.0/src/aictem/walker.py +65 -0
- aictem-0.1.0/tests/conftest.py +97 -0
- aictem-0.1.0/tests/test_discover.py +132 -0
- aictem-0.1.0/tests/test_recheck.py +265 -0
aictem-0.1.0/.gitignore
ADDED
|
@@ -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,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())
|