praesidium 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.
Files changed (32) hide show
  1. praesidium-0.1.0/.env +3 -0
  2. praesidium-0.1.0/.env.example +3 -0
  3. praesidium-0.1.0/.idea/.gitignore +10 -0
  4. praesidium-0.1.0/.idea/OIST-ACM-AOC-CodeCrafters.iml +10 -0
  5. praesidium-0.1.0/.idea/copilot.data.migration.ask2agent.xml +6 -0
  6. praesidium-0.1.0/.idea/inspectionProfiles/Project_Default.xml +81 -0
  7. praesidium-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  8. praesidium-0.1.0/.idea/misc.xml +7 -0
  9. praesidium-0.1.0/.idea/modules.xml +8 -0
  10. praesidium-0.1.0/.idea/vcs.xml +6 -0
  11. praesidium-0.1.0/.idea/workspace.xml +190 -0
  12. praesidium-0.1.0/PKG-INFO +232 -0
  13. praesidium-0.1.0/README.md +204 -0
  14. praesidium-0.1.0/example_cves.json +26 -0
  15. praesidium-0.1.0/pyproject.toml +54 -0
  16. praesidium-0.1.0/scripts/smoke_test.py +51 -0
  17. praesidium-0.1.0/src/__init__.py +4 -0
  18. praesidium-0.1.0/src/cli.py +413 -0
  19. praesidium-0.1.0/src/dynamic_harness.py +103 -0
  20. praesidium-0.1.0/src/graph_builder.py +140 -0
  21. praesidium-0.1.0/src/hypothesis.py +136 -0
  22. praesidium-0.1.0/src/nodes.py +137 -0
  23. praesidium-0.1.0/src/pipeline.py +61 -0
  24. praesidium-0.1.0/src/schemas.py +65 -0
  25. praesidium-0.1.0/tests/fixtures/dummy_target_app/app/__init__.py +0 -0
  26. praesidium-0.1.0/tests/fixtures/dummy_target_app/app/routes.py +13 -0
  27. praesidium-0.1.0/tests/fixtures/dummy_target_app/app/services/__init__.py +0 -0
  28. praesidium-0.1.0/tests/fixtures/dummy_target_app/app/services/upload.py +11 -0
  29. praesidium-0.1.0/tests/test_dynamic_harness.py +31 -0
  30. praesidium-0.1.0/tests/test_graph_builder.py +25 -0
  31. praesidium-0.1.0/tests/test_pipeline_integration.py +56 -0
  32. praesidium-0.1.0/uv.lock +969 -0
praesidium-0.1.0/.env ADDED
@@ -0,0 +1,3 @@
1
+ GROQ_API_KEY='test_key_12345'
2
+ # Optional override, defaults to openai/gpt-oss-120b:
3
+ # GROQ_MODEL=openai/gpt-oss-20b
@@ -0,0 +1,3 @@
1
+ GROQ_API_KEY=gsk_...
2
+ # Optional override, defaults to openai/gpt-oss-120b:
3
+ # GROQ_MODEL=openai/gpt-oss-20b
@@ -0,0 +1,10 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Ignored default folder with query files
5
+ /queries/
6
+ # Datasource local storage ignored files
7
+ /dataSources/
8
+ /dataSources.local.xml
9
+ # Editor-based HTTP Client requests
10
+ /httpRequests/
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+ </content>
7
+ <orderEntry type="jdk" jdkName="uv (OIST-ACM-AOC-CodeCrafters) (2)" jdkType="Python SDK" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Ask2AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,81 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="customHeaders">
6
+ <set>
7
+ <option value="file=@sample.pdf" />
8
+ </set>
9
+ </option>
10
+ </inspection_tool>
11
+ <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
12
+ <option name="ourVersions">
13
+ <value>
14
+ <list size="1">
15
+ <item index="0" class="java.lang.String" itemvalue="3.13" />
16
+ </list>
17
+ </value>
18
+ </option>
19
+ </inspection_tool>
20
+ <inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
21
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false">
22
+ <option name="ignoredPackages">
23
+ <list>
24
+ <option value="annotated-types" />
25
+ <option value="anyio" />
26
+ <option value="asgiref" />
27
+ <option value="cffi" />
28
+ <option value="cryptography" />
29
+ <option value="distro" />
30
+ <option value="dj-database-url" />
31
+ <option value="dj-rest-auth" />
32
+ <option value="Django" />
33
+ <option value="django-allauth" />
34
+ <option value="django-filter" />
35
+ <option value="django-stubs" />
36
+ <option value="django-stubs-ext" />
37
+ <option value="djangorestframework" />
38
+ <option value="djangorestframework-stubs" />
39
+ <option value="djangorestframework_simplejwt" />
40
+ <option value="google-auth" />
41
+ <option value="google-genai" />
42
+ <option value="gunicorn" />
43
+ <option value="h11" />
44
+ <option value="httpcore" />
45
+ <option value="httpx" />
46
+ <option value="psycopg2-binary" />
47
+ <option value="pyasn1" />
48
+ <option value="pyasn1_modules" />
49
+ <option value="pycparser" />
50
+ <option value="pydantic" />
51
+ <option value="pydantic_core" />
52
+ <option value="PyJWT" />
53
+ <option value="python-dotenv" />
54
+ <option value="rsa" />
55
+ <option value="sniffio" />
56
+ <option value="sqlparse" />
57
+ <option value="types-PyYAML" />
58
+ <option value="typing-inspection" />
59
+ <option value="websockets" />
60
+ <option value="django-cors-headers" />
61
+ </list>
62
+ </option>
63
+ </inspection_tool>
64
+ <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
65
+ <option name="ignoredErrors">
66
+ <list>
67
+ <option value="N813" />
68
+ <option value="N803" />
69
+ <option value="N806" />
70
+ </list>
71
+ </option>
72
+ </inspection_tool>
73
+ <inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
74
+ <option name="ignoredPackages">
75
+ <list>
76
+ <option value="pandas" />
77
+ </list>
78
+ </option>
79
+ </inspection_tool>
80
+ </profile>
81
+ </component>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="uv (OIST-ACM-AOC-CodeCrafters) (2)" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="uv (OIST-ACM-AOC-CodeCrafters) (2)" project-jdk-type="Python SDK" />
7
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/OIST-ACM-AOC-CodeCrafters.iml" filepath="$PROJECT_DIR$/.idea/OIST-ACM-AOC-CodeCrafters.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,190 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="1155a69b-3971-4839-9d64-cc45e3d5adad" name="Changes" comment="Added scaffolding">
8
+ <change afterPath="$PROJECT_DIR$/.idea/.gitignore" afterDir="false" />
9
+ <change afterPath="$PROJECT_DIR$/.idea/OIST-ACM-AOC-CodeCrafters.iml" afterDir="false" />
10
+ <change afterPath="$PROJECT_DIR$/.idea/copilot.data.migration.ask2agent.xml" afterDir="false" />
11
+ <change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
12
+ <change afterPath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" afterDir="false" />
13
+ <change afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
14
+ <change afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
15
+ <change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
16
+ <change afterPath="$PROJECT_DIR$/tests/test_pipeline_integration.py" afterDir="false" />
17
+ <change beforePath="$PROJECT_DIR$/.env.example" beforeDir="false" afterPath="$PROJECT_DIR$/.env.example" afterDir="false" />
18
+ <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
19
+ <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
20
+ <change beforePath="$PROJECT_DIR$/scripts/smoke_test.py" beforeDir="false" afterPath="$PROJECT_DIR$/scripts/smoke_test.py" afterDir="false" />
21
+ <change beforePath="$PROJECT_DIR$/src/dynamic_harness.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/dynamic_harness.py" afterDir="false" />
22
+ <change beforePath="$PROJECT_DIR$/src/hypothesis.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/hypothesis.py" afterDir="false" />
23
+ <change beforePath="$PROJECT_DIR$/src/nodes.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/nodes.py" afterDir="false" />
24
+ <change beforePath="$PROJECT_DIR$/tests/test_dynamic_harness.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/test_dynamic_harness.py" afterDir="false" />
25
+ <change beforePath="$PROJECT_DIR$/tests/test_graph_builder.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/test_graph_builder.py" afterDir="false" />
26
+ <change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
27
+ </list>
28
+ <option name="SHOW_DIALOG" value="false" />
29
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
30
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
31
+ <option name="LAST_RESOLUTION" value="IGNORE" />
32
+ </component>
33
+ <component name="CopilotPersistence">
34
+ <persistenceIdMap>
35
+ <entry key="_C:/Users/acer/PycharmProjects/OIST-ACM-AOC-CodeCrafters" value="3GKN3bDyvyIJc6rnK0knBQ8Xu6j" />
36
+ </persistenceIdMap>
37
+ </component>
38
+ <component name="EmbeddingIndexingInfo">
39
+ <option name="cachedIndexableFilesCount" value="9" />
40
+ <option name="fileBasedEmbeddingIndicesEnabled" value="true" />
41
+ </component>
42
+ <component name="FileTemplateManagerImpl">
43
+ <option name="RECENT_TEMPLATES">
44
+ <list>
45
+ <option value="Python Script" />
46
+ </list>
47
+ </option>
48
+ </component>
49
+ <component name="Git.Settings">
50
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
51
+ </component>
52
+ <component name="GithubDefaultAccount">
53
+ <option name="defaultAccountId" value="e896eba7-bbd3-45ba-8410-e73e589fe335" />
54
+ </component>
55
+ <component name="McpProjectServerCommands">
56
+ <commands />
57
+ <urls />
58
+ </component>
59
+ <component name="ProjectColorInfo"><![CDATA[{
60
+ "associatedIndex": 2
61
+ }]]></component>
62
+ <component name="ProjectId" id="3GKN3bDyvyIJc6rnK0knBQ8Xu6j" />
63
+ <component name="ProjectViewState">
64
+ <option name="hideEmptyMiddlePackages" value="true" />
65
+ <option name="showLibraryContents" value="true" />
66
+ </component>
67
+ <component name="PropertiesComponent"><![CDATA[{
68
+ "keyToString": {
69
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
70
+ "Python tests.Python tests for tests.test_dynamic_harness.test_patch_logs_invocation_when_reached.executor": "Run",
71
+ "Python.smoke_test.executor": "Run",
72
+ "RunOnceActivity.MCP Project settings loaded": "true",
73
+ "RunOnceActivity.ShowReadmeOnStart": "true",
74
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
75
+ "RunOnceActivity.git.unshallow": "true",
76
+ "RunOnceActivity.typescript.service.memoryLimit.init": "true",
77
+ "SHARE_PROJECT_CONFIGURATION_FILES": "true",
78
+ "ai.playground.ignore.import.keys.banner.in.settings": "true",
79
+ "git-widget-placeholder": "master",
80
+ "last_opened_file_path": "C:/Users/acer/PycharmProjects/OIST-ACM-AOC-CodeCrafters/src",
81
+ "node.js.detected.package.eslint": "true",
82
+ "node.js.detected.package.tslint": "true",
83
+ "node.js.selected.package.eslint": "(autodetect)",
84
+ "node.js.selected.package.tslint": "(autodetect)",
85
+ "nodejs_package_manager_path": "npm",
86
+ "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
87
+ "vue.rearranger.settings.migration": "true"
88
+ }
89
+ }]]></component>
90
+ <component name="RecentsManager">
91
+ <key name="CopyFile.RECENT_KEYS">
92
+ <recent name="C:\Users\acer\PycharmProjects\OIST-ACM-AOC-CodeCrafters\src" />
93
+ <recent name="C:\Users\acer\PycharmProjects\OIST-ACM-AOC-CodeCrafters" />
94
+ <recent name="C:\Users\acer\PycharmProjects\OIST-ACM-AOC-CodeCrafters\scripts" />
95
+ <recent name="C:\Users\acer\PycharmProjects\OIST-ACM-AOC-CodeCrafters\tests" />
96
+ </key>
97
+ <key name="MoveFile.RECENT_KEYS">
98
+ <recent name="C:\Users\acer\PycharmProjects\OIST-ACM-AOC-CodeCrafters\src" />
99
+ </key>
100
+ </component>
101
+ <component name="RunManager">
102
+ <configuration name="Python tests for tests.test_dynamic_harness.test_patch_logs_invocation_when_reached" type="tests" factoryName="Autodetect" temporary="true" nameIsGenerated="true">
103
+ <module name="OIST-ACM-AOC-CodeCrafters" />
104
+ <option name="ENV_FILES" value="" />
105
+ <option name="INTERPRETER_OPTIONS" value="" />
106
+ <option name="PARENT_ENVS" value="true" />
107
+ <option name="SDK_HOME" value="" />
108
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
109
+ <option name="IS_MODULE_SDK" value="true" />
110
+ <option name="ADD_CONTENT_ROOTS" value="true" />
111
+ <option name="ADD_SOURCE_ROOTS" value="true" />
112
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
113
+ <option name="RUN_TOOL" value="" />
114
+ <option name="_new_additionalArguments" value="&quot;&quot;" />
115
+ <option name="_new_target" value="&quot;tests.test_dynamic_harness.test_patch_logs_invocation_when_reached&quot;" />
116
+ <option name="_new_targetType" value="&quot;PYTHON&quot;" />
117
+ <method v="2" />
118
+ </configuration>
119
+ <recent_temporary>
120
+ <list>
121
+ <item itemvalue="Python tests.Python tests for tests.test_dynamic_harness.test_patch_logs_invocation_when_reached" />
122
+ </list>
123
+ </recent_temporary>
124
+ </component>
125
+ <component name="SharedIndexes">
126
+ <attachedChunks>
127
+ <set>
128
+ <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-PY-253.31033.139" />
129
+ <option value="bundled-python-sdk-2653e85de345-6d6dccd035ac-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.31033.139" />
130
+ </set>
131
+ </attachedChunks>
132
+ </component>
133
+ <component name="TaskManager">
134
+ <task active="true" id="Default" summary="Default task">
135
+ <changelist id="1155a69b-3971-4839-9d64-cc45e3d5adad" name="Changes" comment="" />
136
+ <created>1783716103926</created>
137
+ <option name="number" value="Default" />
138
+ <option name="presentableId" value="Default" />
139
+ <updated>1783716103926</updated>
140
+ <workItem from="1783716105610" duration="6629000" />
141
+ </task>
142
+ <task id="LOCAL-00001" summary="Initial commit">
143
+ <option name="closed" value="true" />
144
+ <created>1783716243199</created>
145
+ <option name="number" value="00001" />
146
+ <option name="presentableId" value="LOCAL-00001" />
147
+ <option name="project" value="LOCAL" />
148
+ <updated>1783716243199</updated>
149
+ </task>
150
+ <task id="LOCAL-00002" summary="Added dependencies">
151
+ <option name="closed" value="true" />
152
+ <created>1783716359805</created>
153
+ <option name="number" value="00002" />
154
+ <option name="presentableId" value="LOCAL-00002" />
155
+ <option name="project" value="LOCAL" />
156
+ <updated>1783716359805</updated>
157
+ </task>
158
+ <task id="LOCAL-00003" summary="Added scaffolding">
159
+ <option name="closed" value="true" />
160
+ <created>1783717758111</created>
161
+ <option name="number" value="00003" />
162
+ <option name="presentableId" value="LOCAL-00003" />
163
+ <option name="project" value="LOCAL" />
164
+ <updated>1783717758111</updated>
165
+ </task>
166
+ <option name="localTasksCounter" value="4" />
167
+ <servers />
168
+ </component>
169
+ <component name="TypeScriptGeneratedFilesManager">
170
+ <option name="version" value="3" />
171
+ </component>
172
+ <component name="VcsManagerConfiguration">
173
+ <MESSAGE value="Initial commit" />
174
+ <MESSAGE value="Added dependencies" />
175
+ <MESSAGE value="Added scaffolding" />
176
+ <option name="LAST_COMMIT_MESSAGE" value="Added scaffolding" />
177
+ </component>
178
+ <component name="com.intellij.coverage.CoverageDataManagerImpl">
179
+ <SUITE FILE_PATH="coverage/OIST_ACM_AOC_CodeCrafters$.coverage" NAME=" Coverage Results" MODIFIED="1783720083456" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
180
+ <SUITE FILE_PATH="coverage/OIST_ACM_AOC_CodeCrafters$smoke_test.coverage" NAME="smoke_test Coverage Results" MODIFIED="1783719757542" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/scripts" />
181
+ </component>
182
+ <component name="github-copilot-workspace">
183
+ <instructionFileLocations>
184
+ <option value=".github/instructions" />
185
+ </instructionFileLocations>
186
+ <promptFileLocations>
187
+ <option value=".github/prompts" />
188
+ </promptFileLocations>
189
+ </component>
190
+ </project>
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: praesidium
3
+ Version: 0.1.0
4
+ Summary: Praesidium — dynamic CVE reachability confirmation engine
5
+ Project-URL: Homepage, https://github.com/TheCodeKage/praesidium
6
+ Project-URL: Repository, https://github.com/TheCodeKage/praesidium
7
+ Project-URL: Issues, https://github.com/TheCodeKage/praesidium/issues
8
+ Author-email: Naman Agrawal <nam.agr96@gmail.com>
9
+ License: MIT
10
+ Keywords: cve,reachability,security,static-analysis,vulnerability
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Security
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.13
19
+ Requires-Dist: click>=8.1.0
20
+ Requires-Dist: groq>=0.13.0
21
+ Requires-Dist: langgraph>=0.2.0
22
+ Requires-Dist: pydantic>=2.9.0
23
+ Requires-Dist: python-dotenv>=1.0.1
24
+ Provides-Extra: dev
25
+ Requires-Dist: flask>=3.0.0; extra == 'dev'
26
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Praesidium
30
+
31
+ **Praesidium** is a dynamic CVE reachability confirmation engine that combines static analysis with LLM-powered hypothesis generation to determine if vulnerabilities are actually exploitable in your codebase.
32
+
33
+ ## Features
34
+
35
+ - 🔍 **Static Call Graph Analysis** - Traces code paths from entry points to vulnerable functions
36
+ - 🤖 **LLM-Powered Hypothesis Generation** - Uses Groq's GPT models to generate realistic exploit attempts
37
+ - 🎯 **Dynamic Confirmation** - Monkeypatches vulnerable functions to confirm actual reachability
38
+ - 📊 **Multi-CVE Support** - Analyze multiple CVEs against your codebase in one command
39
+ - 🔑 **Easy Configuration** - Simple CLI for API key management
40
+ - 📈 **FastAPI/Flask Support** - Works with popular Python web frameworks
41
+
42
+ ## Setup
43
+
44
+ ```bash
45
+ uv sync --extra dev
46
+ cp .env.example .env # fill in GROQ_API_KEY (get one at console.groq.com)
47
+ ```
48
+
49
+ ## Run tests
50
+
51
+ ```bash
52
+ uv run pytest -v
53
+ ```
54
+
55
+ `test_graph_builder.py` and `test_dynamic_harness.py` run against the dummy
56
+ fixture app in `tests/fixtures/dummy_target_app/` and need no API key —
57
+ these are your fast inner-loop tests while building Hour 0:30–3:30.
58
+
59
+ ## Run the full pipeline (needs Groq API key)
60
+
61
+ ```bash
62
+ uv run python scripts/smoke_test.py
63
+ ```
64
+
65
+ Runs both a reachable and a not-reachable case against the dummy fixture,
66
+ printing the full trace. This is your first real integration check before
67
+ Checkpoint 1 with Engineer B — swap the dummy fixture for the real target
68
+ app + real CVE once B has it ready.
69
+
70
+ ## CLI Usage
71
+
72
+ ### Installation
73
+
74
+ ```bash
75
+ # Install from this directory
76
+ uv pip install -e .
77
+
78
+ # Or install from PyPI (once published)
79
+ pip install praesidium
80
+ ```
81
+
82
+ ### Quick Start
83
+
84
+ #### 1. Configure API Key
85
+
86
+ ```bash
87
+ # Set your Groq API key (get one at console.groq.com)
88
+ uv run praesidium config set-key gsk_your_api_key_here
89
+
90
+ # Verify configuration
91
+ uv run praesidium config show
92
+ ```
93
+
94
+ #### 2. Analyze Multiple CVEs
95
+
96
+ Create a JSON file with your CVE configurations:
97
+
98
+ ```json
99
+ [
100
+ {
101
+ "cve_id": "CVE-2023-12345",
102
+ "flagged_function": "save_file",
103
+ "flagged_module": "app.services.upload",
104
+ "flagged_file": "app/services/upload.py",
105
+ "entry_points": ["app.routes.upload_endpoint"],
106
+ "advisory_summary": "Path traversal vulnerability",
107
+ "function_signature": "def save_file(filename, contents)"
108
+ },
109
+ {
110
+ "cve_id": "CVE-2023-67890",
111
+ "flagged_function": "process_data",
112
+ "flagged_module": "app.services.data",
113
+ "flagged_file": "app/services/data.py",
114
+ "entry_points": ["app.routes.data_endpoint"],
115
+ "advisory_summary": "SQL injection vulnerability"
116
+ }
117
+ ]
118
+ ```
119
+
120
+ Run the analysis:
121
+
122
+ ```bash
123
+ # Analyze current directory
124
+ uv run praesidium run --cves cves.json
125
+
126
+ # Analyze specific directory
127
+ uv run praesidium run --cves cves.json --target /path/to/app
128
+
129
+ # Get summary only
130
+ uv run praesidium run --cves cves.json --summary
131
+
132
+ # Output as JSON
133
+ uv run praesidium run --cves cves.json --output json > results.json
134
+ ```
135
+
136
+ #### 3. Check Single CVE
137
+
138
+ For single CVE analysis:
139
+
140
+ ```bash
141
+ # Generate sample config
142
+ uv run praesidium init-config cve.json
143
+
144
+ # Edit the config, then run
145
+ uv run praesidium check /path/to/app --cve-config cve.json
146
+ ```
147
+
148
+ Or use command-line options:
149
+
150
+ ```bash
151
+ uv run praesidium check /path/to/app \
152
+ --cve-id CVE-2023-12345 \
153
+ --flagged-function save_file \
154
+ --flagged-module app.services.upload \
155
+ --flagged-file app/services/upload.py \
156
+ --entry-points app.routes.upload_endpoint \
157
+ --advisory-summary "Path traversal vulnerability"
158
+ ```
159
+
160
+ ### Available Commands
161
+
162
+ - `praesidium config set-key <API_KEY>` - Configure Groq API key
163
+ - `praesidium config show` - Display current configuration
164
+ - `praesidium run --cves <FILE>` - Analyze multiple CVEs from JSON file
165
+ - `praesidium check <PATH>` - Check single CVE against target app
166
+ - `praesidium init-config <FILE>` - Generate sample CVE config file
167
+
168
+ ## Python API (the interface contract)
169
+
170
+ ```python
171
+ from src import CVETarget, run_pipeline
172
+
173
+ target = CVETarget(
174
+ cve_id="CVE-2024-XXXXX",
175
+ flagged_function="...",
176
+ flagged_module="...",
177
+ flagged_file="...",
178
+ entry_points=["..."],
179
+ advisory_summary="...",
180
+ function_signature="...",
181
+ )
182
+
183
+ result = run_pipeline(target, target_app_root="/path/to/target/app")
184
+ # result: EngineOutput(cve_id, label, trace, static_path, hypothesis_attempts)
185
+ ```
186
+
187
+ `CVETarget` and `EngineOutput` in `schemas.py` are the two shapes from
188
+ build-plan.md Section 2 — sync any changes to these with Engineer B
189
+ immediately, don't let them drift silently.
190
+
191
+ ## LLM provider
192
+
193
+ Hypothesis generation (`hypothesis.py`) calls Groq's OpenAI-compatible chat
194
+ completions endpoint, using `openai/gpt-oss-120b` by default (Groq's
195
+ current recommended production model for reasoning workloads — the earlier
196
+ `llama-3.3-70b-versatile` was deprecated). Override with `GROQ_MODEL` in
197
+ `.env` if you want to try `openai/gpt-oss-20b` for lower latency during
198
+ iteration. Groq's speed matters here specifically because Stage 2 can fire
199
+ up to 3 calls per CVE (1 initial + 2 retries) inside a live demo.
200
+
201
+ ## Publishing to PyPI
202
+
203
+ To publish Praesidium to PyPI:
204
+
205
+ ```bash
206
+ # 1. Update version in pyproject.toml
207
+
208
+ # 2. Build the package
209
+ uv build
210
+
211
+ # 3. Test on TestPyPI first (recommended)
212
+ uv pip install twine
213
+ uv run twine upload --repository testpypi dist/*
214
+
215
+ # 4. Test install from TestPyPI
216
+ pip install --index-url https://test.pypi.org/simple/ praesidium
217
+
218
+ # 5. If everything works, publish to PyPI
219
+ uv run twine upload dist/*
220
+ ```
221
+
222
+ **Note:** You'll need PyPI accounts and API tokens. See [PyPI documentation](https://packaging.python.org/tutorials/packaging-projects/) for details.
223
+
224
+ ## Known scope boundaries (by design, not oversight)
225
+
226
+ - Static graph resolution is name-based, not points-to analysis — decorators
227
+ and dynamic dispatch can produce false negatives at Stage 1. This is why
228
+ Stage 2 exists; don't try to fix it in `graph_builder.py`.
229
+ - Retry cap is hard-set to 2 attempts (`nodes.py::MAX_RETRIES`) to keep demo
230
+ runtime bounded. Don't raise it without checking demo timing.
231
+ - `EntryPointDriver` supports `callable` and `flask_route` shapes only. If
232
+ B's fixture needs CLI-arg driving, extend `dynamic_harness.py`.