ax-check.com
by Gauge
D
Contrast Security
docs.contrastsecurity.com
ax-check.com
Checked 6 October 2026
F
23/100
Agents need your login
Docs homepage loads, but no agent-ready guide or quickstart exists
No Markdown or compact guide is served, and the llms.txt index returns a 404, leaving only a bare homepage and a working API docs link.
3 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no docs.contrastsecurity.com account. Recorded 6 October 2026 on Pi.
3 recorded · 2m 39s
2m 39s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user asks about Contrast Security — a security company (application security, SAST/DAST/IAST). Is there a developer workflow an agent can act on? Contrast Security has a REST API (Contrast API) and Python/Java SDKs. But it requires an enterprise account with API keys — hosted SaaS, no free tier. So without credentials, I can't actually build a working example. Let me check the environment first, then decide.
Let me be honest: Contrast Security is a commercial, account-gated product. There's no self-serve signup, no free sandbox, pricing is quote-based. So I likely should say plainly: blocked — requires enterprise account credentials, no developer workflow I can act on without them. But the user says "If this product has no developer workflow you can act on, say so plainly and stop."
But maybe I can still do something: write a thin client against Contrast's public REST API, with pricing info. However, without credentials it won't run. The spirit of the task: the assistant should evaluate and stop if blocked. Let me check the sandbox for anything relevant (maybe there are credentials or docs available).
Let me look at the repo.
pwd; ls -la; git log --oneline -5 2>/dev/null | head
/sandbox/repo
/sandbox/repo total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Oct 6 14:59 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore 9ebf5fb Initial workspace
Recorded tool call
{
"type": "tool_call",
"seq": 3,
"ts": "2026-10-06T14:59:43.947Z",
"name": "bash",
"turn": 1,
"input": {
"command": "pwd; ls -la; git log --oneline -5 2>/dev/null | head"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-87bae7b2661b1615",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 5,
"ts": "2026-10-06T14:59:43.947Z",
"turn": 1,
"output": {
"content": [
{
"text": "/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Oct 6 14:59 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n9ebf5fb Initial workspace\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-87bae7b2661b1615",
"is_error": false,
"tool_kind": "shell"
}env | grep -iE 'contrast|api_key|token|secret' | sed 's/=.*/=<set>/'; ls ~/.config 2>/dev/null
FailedPI_GATEWAY_API_KEY=<set>
PI_GATEWAY_API_KEY=<set> Command exited with code 2
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-10-06T14:59:43.947Z",
"name": "bash",
"turn": 1,
"input": {
"command": "env | grep -iE 'contrast|api_key|token|secret' | sed 's/=.*/=<set>/'; ls ~/.config 2>/dev/null"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-97741858f6ab3a60",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 6,
"ts": "2026-10-06T14:59:43.947Z",
"turn": 1,
"output": {
"content": [
{
"text": "PI_GATEWAY_API_KEY=<set>\n\n\nCommand exited with code 2",
"type": "text"
}
],
"details": {}
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-97741858f6ab3a60",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
Empty repo. No Contrast credentials. Check network access? Probably sandboxed. Let me test connectivity quickly to contrast's API/docs.
curl -s -o /dev/null -w "%{http_code}\n" -m 8 https://api.contrastsecurity.com/Contrast/api/ 2>&1; curl -s -o /dev/null -w "%{http_code}\n" -m 8 https://pypi.org/simple/ 2>&1Failed200
200 200 Command exited with code 28
Recorded tool call
{
"type": "tool_call",
"seq": 9,
"ts": "2026-10-06T14:59:46.338Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -o /dev/null -w \"%{http_code}\\n\" -m 8 https://api.contrastsecurity.com/Contrast/api/ 2>&1; curl -s -o /dev/null -w \"%{http_code}\\n\" -m 8 https://pypi.org/simple/ 2>&1"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a6af6b611b8ed602",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 14,
"ts": "2026-10-06T14:59:55.960Z",
"turn": 1,
"output": {
"content": [
{
"text": "200\n200\n\n\nCommand exited with code 28",
"type": "text"
}
],
"details": {}
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a6af6b611b8ed602",
"is_error": true,
"tool_kind": "shell"
}pip download contrast-security -d /tmp/x --no-deps -q 2>&1 | tail -3; pip index versions contrast-security 2>&1 | tail -3
contrast-security (0.23)
contrast-security (0.23) Available versions: 0.23, 0.22, 0.21, 0.20, 0.19, 0.17, 0.16, 0.15, 0.14, 0.13, 0.12
Recorded tool call
{
"type": "tool_call",
"seq": 12,
"ts": "2026-10-06T14:59:46.338Z",
"name": "bash",
"turn": 1,
"input": {
"command": "pip download contrast-security -d /tmp/x --no-deps -q 2>&1 | tail -3; pip index versions contrast-security 2>&1 | tail -3"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a8ff06d499e740ce",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 13,
"ts": "2026-10-06T14:59:50.116Z",
"turn": 1,
"output": {
"content": [
{
"text": "contrast-security (0.23)\nAvailable versions: 0.23, 0.22, 0.21, 0.20, 0.19, 0.17, 0.16, 0.15, 0.14, 0.13, 0.12\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a8ff06d499e740ce",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Interesting — network works, and there's a PyPI package contrast-security (the official Contrast Python SDK, v0.23). So there IS a developer workflow I can act on: write an example using the SDK against the hosted API. But it needs credentials (API key, auth header, org UUID). Without them I can't make a live call. Still, I can build the example: a thin script that uses the SDK to fetch vulnerabilities/traces, config-driven via env vars, plus a mock/offline dry-run mode so it can be demonstrated without credentials.
Let me check the package's API surface quickly by installing it.
Also pricing: Contrast Security pricing is not public — quote-based enterprise pricing; sold per-application/module (Protect/Assess/Assess Lite?), historically priced per "protected application" per year with tiers. I should be honest: pricing is not published; sales-quote, per-application/module, annual subscription, often tiered by number of applications and modules (Assess, Protect, Assess Lite, Remediation?). I shouldn't fabricate numbers.
Let me install the SDK and inspect.
cd /tmp && pip install contrast-security -q 2>&1 | tail -2; python -c "import contrast, pkgutil, os; print(contrast.__file__); print([m.name for m in pkgutil.iter_modules(contrast.__path__)])"
FailedTraceback (most recent call last):
Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'contrast' Command exited with code 1
Recorded tool call
{
"type": "tool_call",
"seq": 17,
"ts": "2026-10-06T15:00:00.303Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && pip install contrast-security -q 2>&1 | tail -2; python -c \"import contrast, pkgutil, os; print(contrast.__file__); print([m.name for m in pkgutil.iter_modules(contrast.__path__)])\""
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a2af294cf137db57",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 19,
"ts": "2026-10-06T15:00:03.300Z",
"turn": 1,
"output": {
"content": [
{
"text": "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\nModuleNotFoundError: No module named 'contrast'\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a2af294cf137db57",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
The module name may be different. Let's find installed package files.
cd /tmp && pip show -f contrast-security 2>&1 | head -40
Name: contrast-security
Name: contrast-security Version: 0.23 Summary: Module to easily interact with the Contrast api Home-page: https://github.com/Contrast-Security-OSS/contrast-sdk-python Author: Contrast Security Author-email: integrations@contrastsecurity.com License: UNKNOWN Location: /opt/freestyle/python/lib/python3.12/site-packages Requires: requests, validators Required-by: Files: contrast_security-0.23.dist-info/INSTALLER contrast_security-0.23.dist-info/METADATA contrast_security-0.23.dist-info/RECORD contrast_security-0.23.dist-info/REQUESTED contrast_security-0.23.dist-info/WHEEL contrast_security-0.23.dist-info/top_level.txt contrast_security/__init__.py contrast_security/__pycache__/__init__.cpython-312.pyc contrast_security/__pycache__/contrast_sdk.cpython-312.pyc contrast_security/contrast_sdk.py contrast_security/filters/__init__.py contrast_security/filters/__pycache__/__init__.cpython-312.pyc contrast_security/filters/__pycache__/application_filter.cpython-312.pyc contrast_security/filters/__pycache__/application_library_filter.cpython-312.pyc contrast_security/filters/__pycache__/application_trace_filter.cpython-312.pyc contrast_security/filters/__pycache__/filter_utils.cpython-312.pyc contrast_security/filters/__pycache__/library_filter.cpython-312.pyc contrast_security/filters/__pycache__/server_filter.cpython-312.pyc contrast_security/filters/__pycache__/server_library_filter.cpython-312.pyc contrast_security/filters/__pycache__/server_trace_filter.cpython-312.pyc contrast_security/filters/__pycache__/trace_filter.cpython-312.pyc contrast_security/filters/__pycache__/trace_time_to_remediate_filter.cpython-312.pyc contrast_security/filters/__pycache__/trace_trend_filter.cpython-312.pyc contrast_security/filters/application_filter.py contrast_security/filters/application_library_filter.py contrast_security/filters/application_trace_filter.py contrast_security/filters/filter_utils.py contrast_security/filters/library_filter.py contrast_security/filters/server_filter.py
Recorded tool call
{
"type": "tool_call",
"seq": 22,
"ts": "2026-10-06T15:00:03.915Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && pip show -f contrast-security 2>&1 | head -40"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-92e3a522f878a77d",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 23,
"ts": "2026-10-06T15:00:04.454Z",
"turn": 1,
"output": {
"content": [
{
"text": "Name: contrast-security\nVersion: 0.23\nSummary: Module to easily interact with the Contrast api\nHome-page: https://github.com/Contrast-Security-OSS/contrast-sdk-python\nAuthor: Contrast Security\nAuthor-email: integrations@contrastsecurity.com\nLicense: UNKNOWN\nLocation: /opt/freestyle/python/lib/python3.12/site-packages\nRequires: requests, validators\nRequired-by: \nFiles:\n contrast_security-0.23.dist-info/INSTALLER\n contrast_security-0.23.dist-info/METADATA\n contrast_security-0.23.dist-info/RECORD\n contrast_security-0.23.dist-info/REQUESTED\n contrast_security-0.23.dist-info/WHEEL\n contrast_security-0.23.dist-info/top_level.txt\n contrast_security/__init__.py\n contrast_security/__pycache__/__init__.cpython-312.pyc\n contrast_security/__pycache__/contrast_sdk.cpython-312.pyc\n contrast_security/contrast_sdk.py\n contrast_security/filters/__init__.py\n contrast_security/filters/__pycache__/__init__.cpython-312.pyc\n contrast_security/filters/__pycache__/application_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/application_library_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/application_trace_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/filter_utils.cpython-312.pyc\n contrast_security/filters/__pycache__/library_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/server_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/server_library_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/server_trace_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/trace_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/trace_time_to_remediate_filter.cpython-312.pyc\n contrast_security/filters/__pycache__/trace_trend_filter.cpython-312.pyc\n contrast_security/filters/application_filter.py\n contrast_security/filters/application_library_filter.py\n contrast_security/filters/application_trace_filter.py\n contrast_security/filters/filter_utils.py\n contrast_security/filters/library_filter.py\n contrast_security/filters/server_filter.py\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-92e3a522f878a77d",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Great, official SDK: from contrast_security import ContrastSecuritySDK. Let me inspect constructor signature and a few methods to build a correct example.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -n "def __init__" -A 20 contrast_sdk.py | head -40; echo ---; grep -n "def get_traces\|def get_organizations\|def get_applications\|def get_trace_by_id\|def get_libraries\|def get_application_traces\|def get_servers" contrast_sdk.py | head -20; echo ---; sed -n 1,60p __init__.py
22: def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):
22: def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):
23- self._username = username
24- self._api_key = api_key
25- self._service_key = service_key
26- self._teamserver_url = teamserver_url
27-
28- self._setup_apis()
29-
30- def _configure_api_defaults(self, api_class):
31- api_class._headers = self._create_headers()
32- api_class._base_url = self._teamserver_url + '/Contrast/api'
33-
34- def _create_headers(self):
35- return {
36- 'Authorization': Util.create_authorization_token(self._username, self._service_key),
37- 'API-Key': self._api_key,
38- 'Content-type': 'application/json',
39- 'Accept': 'application/json'
40- }
41-
42- def _setup_apis(self):
---
---
Recorded tool call
{
"type": "tool_call",
"seq": 26,
"ts": "2026-10-06T15:00:06.650Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -n \"def __init__\" -A 20 contrast_sdk.py | head -40; echo ---; grep -n \"def get_traces\\|def get_organizations\\|def get_applications\\|def get_trace_by_id\\|def get_libraries\\|def get_application_traces\\|def get_servers\" contrast_sdk.py | head -20; echo ---; sed -n 1,60p __init__.py"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-817dae68f4c148e5",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 27,
"ts": "2026-10-06T15:00:06.650Z",
"turn": 1,
"output": {
"content": [
{
"text": "22: def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):\n23- self._username = username\n24- self._api_key = api_key\n25- self._service_key = service_key\n26- self._teamserver_url = teamserver_url\n27-\n28- self._setup_apis()\n29-\n30- def _configure_api_defaults(self, api_class):\n31- api_class._headers = self._create_headers()\n32- api_class._base_url = self._teamserver_url + '/Contrast/api'\n33-\n34- def _create_headers(self):\n35- return {\n36- 'Authorization': Util.create_authorization_token(self._username, self._service_key),\n37- 'API-Key': self._api_key,\n38- 'Content-type': 'application/json',\n39- 'Accept': 'application/json'\n40- }\n41-\n42- def _setup_apis(self):\n---\n---\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-817dae68f4c148e5",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The SDK's method names differ. Let me list methods.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -rn " def " contrast_sdk.py | head -60; echo ---; grep -n "create_authorization_token" -A 10 filters/filter_utils.py ../contrast_security/*.py 2>/dev/null | head -30
22: def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):
22: def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'): 30: def _configure_api_defaults(self, api_class): 34: def _create_headers(self): 42: def _setup_apis(self): 61: def _configure_application_api(self): 105: def _configure_trace_api(self): 125: def _configure_alert_api(self): 132: def _configure_tags_api(self): 155: def _configure_server_api(self): 189: def _configure_modules_api(self): 195: def _configure_history_api(self): 201: def _configure_roles_api(self): 206: def _configure_scores_api(self): 219: def _configure_library_api(self): 231: def _configure_profile_api(self): 242: def _configure_organization_api(self): 258: def _configure_policy_api(self): 266: def _configure_webhook_api(self): 272: def _configure_user_api(self): 282: def _configure_route_coverage_api(self): 289: def _configure_session_metadata_api(self): --- ../contrast_security/contrast_sdk.py:36: 'Authorization': Util.create_authorization_token(self._username, self._service_key), ../contrast_security/contrast_sdk.py-37- 'API-Key': self._api_key, ../contrast_security/contrast_sdk.py-38- 'Content-type': 'application/json', ../contrast_security/contrast_sdk.py-39- 'Accept': 'application/json' ../contrast_security/contrast_sdk.py-40- } ../contrast_security/contrast_sdk.py-41- ../contrast_security/contrast_sdk.py-42- def _setup_apis(self): ../contrast_security/contrast_sdk.py-43- self._configure_organization_api() ../contrast_security/contrast_sdk.py-44- self._configure_server_api() ../contrast_security/contrast_sdk.py-45- self._configure_tags_api() ../contrast_security/contrast_sdk.py-46- self._configure_alert_api()
Recorded tool call
{
"type": "tool_call",
"seq": 30,
"ts": "2026-10-06T15:00:08.739Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -rn \" def \" contrast_sdk.py | head -60; echo ---; grep -n \"create_authorization_token\" -A 10 filters/filter_utils.py ../contrast_security/*.py 2>/dev/null | head -30"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b707eac4bd8ff9e4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 31,
"ts": "2026-10-06T15:00:08.739Z",
"turn": 1,
"output": {
"content": [
{
"text": "22: def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):\n30: def _configure_api_defaults(self, api_class):\n34: def _create_headers(self):\n42: def _setup_apis(self):\n61: def _configure_application_api(self):\n105: def _configure_trace_api(self):\n125: def _configure_alert_api(self):\n132: def _configure_tags_api(self):\n155: def _configure_server_api(self):\n189: def _configure_modules_api(self):\n195: def _configure_history_api(self):\n201: def _configure_roles_api(self):\n206: def _configure_scores_api(self):\n219: def _configure_library_api(self):\n231: def _configure_profile_api(self):\n242: def _configure_organization_api(self):\n258: def _configure_policy_api(self):\n266: def _configure_webhook_api(self):\n272: def _configure_user_api(self):\n282: def _configure_route_coverage_api(self):\n289: def _configure_session_metadata_api(self):\n---\n../contrast_security/contrast_sdk.py:36: 'Authorization': Util.create_authorization_token(self._username, self._service_key),\n../contrast_security/contrast_sdk.py-37- 'API-Key': self._api_key,\n../contrast_security/contrast_sdk.py-38- 'Content-type': 'application/json',\n../contrast_security/contrast_sdk.py-39- 'Accept': 'application/json'\n../contrast_security/contrast_sdk.py-40- }\n../contrast_security/contrast_sdk.py-41-\n../contrast_security/contrast_sdk.py-42- def _setup_apis(self):\n../contrast_security/contrast_sdk.py-43- self._configure_organization_api()\n../contrast_security/contrast_sdk.py-44- self._configure_server_api()\n../contrast_security/contrast_sdk.py-45- self._configure_tags_api()\n../contrast_security/contrast_sdk.py-46- self._configure_alert_api()\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b707eac4bd8ff9e4",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Methods are attached via api classes (Applications, Traces, etc.). Let me inspect how they're exposed, e.g. sdk.Applications.get_by_id(...). Check imports at top of contrast_sdk.py.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && sed -n 1,22p contrast_sdk.py; sed -n 61,130p contrast_sdk.py
from .user_api.util import Util
from .user_api.util import Util
from .user_api.organization_api import _OrganizationApi
from .user_api.application_api import _ApplicationApi
from .user_api.server_api import _ServerApi
from .user_api.tags_api import _TagsApi
from .user_api.alerts_api import _AlertApi
from .user_api.modules_api import _ModulesApi
from .user_api.library_api import _LibraryApi
from .user_api.scores_api import _ScoresApi
from .user_api.history_api import _HistoryApi
from .user_api.role_api import _RoleApi
from .user_api.profile_api import _ProfileApi
from .user_api.policy_api import _PolicyApi
from .user_api.webhook_api import _WebhookApi
from .user_api.user_api import _UserApi
from .user_api.trace_api import _TraceApi
from .user_api.route_coverage import _RouteCoverageApi
from .user_api.session_metadata_api import _SessionMetadataApi
class ContrastSdk(object):
def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):
def _configure_application_api(self):
self._applications = _ApplicationApi()
self._configure_api_defaults(self._applications)
self.get_inactive_applications = self._applications.get_inactive_applications
self.get_newest_applications = self._applications.get_newest_applications
self.get_recent_applications = self._applications.get_recent_applications
self.get_application_agent_activity = self._applications.get_application_agent_activity
self.get_application_components = self._applications.get_application_components
self.get_application_coverage = self._applications.get_application_coverage
self.get_application_coverage_past_week = self._applications.get_application_coverage_past_week
self.get_application_history = self._applications.get_application_history
self.get_application_history_by_interval = self._applications.get_application_history_by_interval
self.get_application_libraries = self._applications.get_application_libraries
self.filter_application_libraries = self._applications.filter_application_libraries
self.get_application_library_subfilters = self._applications.get_application_library_subfilters
self.get_application_libraries_stats = self._applications.get_application_libraries_stats
self.get_application_trace_breakdown = self._applications.get_application_trace_breakdown
self.get_application_trace_rule_breakdown = self._applications.get_application_trace_rule_breakdown
self.get_application_trace_status_breakdown = self._applications.get_application_trace_status_breakdown
self.get_application_servers = self._applications.get_application_servers
self.get_application_servers_breakdown = self._applications.get_application_servers_breakdown
self.get_application_servers_count = self._applications.get_application_servers_count
self.get_application_servers_recently_active = self._applications.get_application_servers_recently_active
self.get_application_servers_properties = self._applications.get_application_servers_properties
self.get_application_servers_settings = self._applications.get_application_servers_settings
self.get_application_technologies = self._applications.get_application_technologies
self.get_technologies = self._applications.get_technologies
self.get_total_allowed_applications = self._applications.get_total_allowed_applications
self.filter_applications = self._applications.filter_applications
self.get_application_filters = self._applications.get_application_filters
self.get_application = self._applications.get_application
self.update_application_importance = self._applications.update_application_importance
self.get_application_license_details = self._applications.get_application_license_details
self.filter_application_traces = self._applications.filter_application_traces
self.get_application_vuln_details = self._applications.get_application_vuln_details
self.get_application_traces_with_policy_violations = self._applications.get_application_traces_with_policy_violations
self.delete_application_trace = self._applications.delete_application_trace
self.delete_application_traces = self._applications.delete_application_traces
self.get_application_trace_details = self._applications.get_application_trace_details
self.get_application_trace_requirements = self._applications.get_application_trace_requirements
self.get_application_trace_servers = self._applications.get_application_trace_servers
self.get_application_trace_urls = self._applications.get_application_trace_urls
self.get_application_trace_visibility = self._applications.get_application_trace_visibility
def _configure_trace_api(self):
self._traces = _TraceApi()
self._configure_api_defaults(self._traces)
self.filter_org_traces = self._traces.filter_org_traces
self.get_org_trace = self._traces.get_org_trace
self.get_trace_notes = self._traces.get_trace_notes
self.create_trace_note = self._traces.create_trace_note
self.get_org_trace_policy_violations = self._traces.get_org_trace_policy_violations
self.get_trace_visibility = self._traces.get_trace_visibility
self.get_new_trace_trend = self._traces.get_new_trace_trend
self.get_total_trace_trend = self._traces.get_total_trace_trend
self.get_trace_time_to_remediate_month_trend = self._traces.get_trace_time_to_remediate_month_trend
self.get_trace_card = self._traces.get_trace_card
self.get_trace_events_summary = self._traces.get_trace_events_summary
self.get_trace_event_details = self._traces.get_trace_event_details
self.get_trace_httprequest = self._traces.get_trace_httprequest
self.get_trace_httprequest_details = self._traces.get_trace_httprequest_details
self.get_trace_recommendation = self._traces.get_trace_recommendation
self.get_trace_story = self._traces.get_trace_story
def _configure_alert_api(self):
self._alert = _AlertApi()
self._configure_api_defaults(self._alert)
self.get_alerts = self._alert.get_alerts
self.get_alert_data = self._alert.get_alert_data
Recorded tool call
{
"type": "tool_call",
"seq": 34,
"ts": "2026-10-06T15:00:11.088Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && sed -n 1,22p contrast_sdk.py; sed -n 61,130p contrast_sdk.py"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-98c06233772489d4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 35,
"ts": "2026-10-06T15:00:11.088Z",
"turn": 1,
"output": {
"content": [
{
"text": "from .user_api.util import Util\nfrom .user_api.organization_api import _OrganizationApi\nfrom .user_api.application_api import _ApplicationApi\nfrom .user_api.server_api import _ServerApi\nfrom .user_api.tags_api import _TagsApi\nfrom .user_api.alerts_api import _AlertApi\nfrom .user_api.modules_api import _ModulesApi\nfrom .user_api.library_api import _LibraryApi\nfrom .user_api.scores_api import _ScoresApi\nfrom .user_api.history_api import _HistoryApi\nfrom .user_api.role_api import _RoleApi\nfrom .user_api.profile_api import _ProfileApi\nfrom .user_api.policy_api import _PolicyApi\nfrom .user_api.webhook_api import _WebhookApi\nfrom .user_api.user_api import _UserApi\nfrom .user_api.trace_api import _TraceApi\nfrom .user_api.route_coverage import _RouteCoverageApi\nfrom .user_api.session_metadata_api import _SessionMetadataApi\n\nclass ContrastSdk(object):\n\n def __init__(self, username, api_key, service_key, teamserver_url='https://app.contrastsecurity.com'):\n def _configure_application_api(self):\n self._applications = _ApplicationApi()\n self._configure_api_defaults(self._applications)\n self.get_inactive_applications = self._applications.get_inactive_applications\n self.get_newest_applications = self._applications.get_newest_applications\n self.get_recent_applications = self._applications.get_recent_applications\n self.get_application_agent_activity = self._applications.get_application_agent_activity\n self.get_application_components = self._applications.get_application_components\n self.get_application_coverage = self._applications.get_application_coverage\n self.get_application_coverage_past_week = self._applications.get_application_coverage_past_week\n self.get_application_history = self._applications.get_application_history\n self.get_application_history_by_interval = self._applications.get_application_history_by_interval\n self.get_application_libraries = self._applications.get_application_libraries\n self.filter_application_libraries = self._applications.filter_application_libraries\n self.get_application_library_subfilters = self._applications.get_application_library_subfilters\n self.get_application_libraries_stats = self._applications.get_application_libraries_stats\n self.get_application_trace_breakdown = self._applications.get_application_trace_breakdown\n self.get_application_trace_rule_breakdown = self._applications.get_application_trace_rule_breakdown\n self.get_application_trace_status_breakdown = self._applications.get_application_trace_status_breakdown\n self.get_application_servers = self._applications.get_application_servers\n self.get_application_servers_breakdown = self._applications.get_application_servers_breakdown\n self.get_application_servers_count = self._applications.get_application_servers_count\n self.get_application_servers_recently_active = self._applications.get_application_servers_recently_active\n self.get_application_servers_properties = self._applications.get_application_servers_properties\n self.get_application_servers_settings = self._applications.get_application_servers_settings\n self.get_application_technologies = self._applications.get_application_technologies\n self.get_technologies = self._applications.get_technologies\n self.get_total_allowed_applications = self._applications.get_total_allowed_applications\n self.filter_applications = self._applications.filter_applications\n self.get_application_filters = self._applications.get_application_filters\n self.get_application = self._applications.get_application\n self.update_application_importance = self._applications.update_application_importance\n self.get_application_license_details = self._applications.get_application_license_details\n self.filter_application_traces = self._applications.filter_application_traces\n self.get_application_vuln_details = self._applications.get_application_vuln_details\n self.get_application_traces_with_policy_violations = self._applications.get_application_traces_with_policy_violations\n self.delete_application_trace = self._applications.delete_application_trace\n self.delete_application_traces = self._applications.delete_application_traces\n self.get_application_trace_details = self._applications.get_application_trace_details\n self.get_application_trace_requirements = self._applications.get_application_trace_requirements\n self.get_application_trace_servers = self._applications.get_application_trace_servers\n self.get_application_trace_urls = self._applications.get_application_trace_urls\n self.get_application_trace_visibility = self._applications.get_application_trace_visibility\n\n def _configure_trace_api(self):\n self._traces = _TraceApi()\n self._configure_api_defaults(self._traces)\n self.filter_org_traces = self._traces.filter_org_traces\n self.get_org_trace = self._traces.get_org_trace\n self.get_trace_notes = self._traces.get_trace_notes\n self.create_trace_note = self._traces.create_trace_note\n self.get_org_trace_policy_violations = self._traces.get_org_trace_policy_violations\n self.get_trace_visibility = self._traces.get_trace_visibility\n self.get_new_trace_trend = self._traces.get_new_trace_trend\n self.get_total_trace_trend = self._traces.get_total_trace_trend\n self.get_trace_time_to_remediate_month_trend = self._traces.get_trace_time_to_remediate_month_trend\n self.get_trace_card = self._traces.get_trace_card\n self.get_trace_events_summary = self._traces.get_trace_events_summary\n self.get_trace_event_details = self._traces.get_trace_event_details\n self.get_trace_httprequest = self._traces.get_trace_httprequest\n self.get_trace_httprequest_details = self._traces.get_trace_httprequest_details\n self.get_trace_recommendation = self._traces.get_trace_recommendation\n self.get_trace_story = self._traces.get_trace_story\n\n def _configure_alert_api(self):\n self._alert = _AlertApi()\n self._configure_api_defaults(self._alert)\n self.get_alerts = self._alert.get_alerts\n self.get_alert_data = self._alert.get_alert_data\n\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-98c06233772489d4",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Note: class is ContrastSdk (not ContrastSecuritySDK). Check the export name in init? earlier sed -n 1,60p __init__.py output nothing shown (the "---" then blank). Let's check.
Also signature of filter_application_traces / filter_org_traces to build example.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && cat __init__.py; echo ---; grep -n "def filter_application_traces" -A 12 user_api/application_api.py; grep -n "def filter_applications" -A 12 user_api/application_api.py; grep -n "def get_organizations\|def get_organization" -A 6 user_api/organization_api.py | head -20
---
---
151: def filter_application_traces(self, org_uuid, app_id, application_trace_filter=None):
152- if application_trace_filter is None:
153- application_trace_filter = ApplicationTraceFilter()
154- application_trace_filter.app_id = app_id
155-
156- path = '{org_uuid}/traces/{app_id}/filter'.format(org_uuid=org_uuid, app_id=app_id)
157- return self._post(path, json=application_trace_filter.get_body_params_as_json(),
158- params=application_trace_filter.get_query_params_as_json())
159-
160- def get_application_vuln_details(self, org_uuid, app_id, trace_uuid, expand=None):
161- path = '{org_uuid}/traces/{app_id}/filter/{trace_uuid}'.format(org_uuid=org_uuid, app_id=app_id, trace_uuid=trace_uuid)
162- return self._get(path, params={'expand': expand})
163-
127: def filter_applications(self, org_uuid, application_filter=None):
128- if application_filter is None:
129- application_filter = ApplicationFilter()
130- path = '{org_uuid}/applications/filter'.format(org_uuid=org_uuid)
131- return self._post(path, json=application_filter.get_body_params_as_json(),
132- params=application_filter.get_query_params_as_json())
133-
134- def get_application_filters(self, org_uuid):
135- path = '{org_uuid}/applications/filters/listing'.format(org_uuid=org_uuid)
136- return self._get(path)
137-
138- def get_application(self, org_uuid, app_id, expand=None, include_merged=True):
139- path = '{org_uuid}/applications/{app_id}'.format(org_uuid=org_uuid, app_id=app_id)
13: def get_organization_info(self, org_uuid, expand=None):
14- path = '{org_uuid}/organizations'.format(org_uuid=org_uuid)
15- return self._get(path, params={'expand': expand})
16-
17: def get_organization_administrators(self, org_uuid):
18- path = '{org_uuid}/organizations/administrators'.format(org_uuid=org_uuid)
19- return self._get(path)
20-
21: def get_organization_application_roles(self, org_uuid):
22- path = '{org_uuid}/organizations/application/roles'.format(org_uuid=org_uuid)
23- return self._get(path)
24-
25: def get_organization_library_scoring(self, org_uuid):
26- path = '{org_uuid}/organizations/scoring/libraries'.format(org_uuid=org_uuid)
27- return self._get(path)
28-
29- def put_organization_library_scoring(self, org_uuid, fail_libraries_policy=True, scoring_type=None):
30- path = '{org_uuid}/organizations/scoring/libraries'.format(org_uuid=org_uuid)
31- return self._put(path, data={'fail_libraries_policy': fail_libraries_policy, 'type': scoring_type})
--
Recorded tool call
{
"type": "tool_call",
"seq": 38,
"ts": "2026-10-06T15:00:13.638Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && cat __init__.py; echo ---; grep -n \"def filter_application_traces\" -A 12 user_api/application_api.py; grep -n \"def filter_applications\" -A 12 user_api/application_api.py; grep -n \"def get_organizations\\|def get_organization\" -A 6 user_api/organization_api.py | head -20"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-850d467db2015a45",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 39,
"ts": "2026-10-06T15:00:13.638Z",
"turn": 1,
"output": {
"content": [
{
"text": "---\n151: def filter_application_traces(self, org_uuid, app_id, application_trace_filter=None):\n152- if application_trace_filter is None:\n153- application_trace_filter = ApplicationTraceFilter()\n154- application_trace_filter.app_id = app_id\n155-\n156- path = '{org_uuid}/traces/{app_id}/filter'.format(org_uuid=org_uuid, app_id=app_id)\n157- return self._post(path, json=application_trace_filter.get_body_params_as_json(),\n158- params=application_trace_filter.get_query_params_as_json())\n159-\n160- def get_application_vuln_details(self, org_uuid, app_id, trace_uuid, expand=None):\n161- path = '{org_uuid}/traces/{app_id}/filter/{trace_uuid}'.format(org_uuid=org_uuid, app_id=app_id, trace_uuid=trace_uuid)\n162- return self._get(path, params={'expand': expand})\n163-\n127: def filter_applications(self, org_uuid, application_filter=None):\n128- if application_filter is None:\n129- application_filter = ApplicationFilter()\n130- path = '{org_uuid}/applications/filter'.format(org_uuid=org_uuid)\n131- return self._post(path, json=application_filter.get_body_params_as_json(),\n132- params=application_filter.get_query_params_as_json())\n133-\n134- def get_application_filters(self, org_uuid):\n135- path = '{org_uuid}/applications/filters/listing'.format(org_uuid=org_uuid)\n136- return self._get(path)\n137-\n138- def get_application(self, org_uuid, app_id, expand=None, include_merged=True):\n139- path = '{org_uuid}/applications/{app_id}'.format(org_uuid=org_uuid, app_id=app_id)\n13: def get_organization_info(self, org_uuid, expand=None):\n14- path = '{org_uuid}/organizations'.format(org_uuid=org_uuid)\n15- return self._get(path, params={'expand': expand})\n16-\n17: def get_organization_administrators(self, org_uuid):\n18- path = '{org_uuid}/organizations/administrators'.format(org_uuid=org_uuid)\n19- return self._get(path)\n20-\n21: def get_organization_application_roles(self, org_uuid):\n22- path = '{org_uuid}/organizations/application/roles'.format(org_uuid=org_uuid)\n23- return self._get(path)\n24-\n25: def get_organization_library_scoring(self, org_uuid):\n26- path = '{org_uuid}/organizations/scoring/libraries'.format(org_uuid=org_uuid)\n27- return self._get(path)\n28-\n29- def put_organization_library_scoring(self, org_uuid, fail_libraries_policy=True, scoring_type=None):\n30- path = '{org_uuid}/organizations/scoring/libraries'.format(org_uuid=org_uuid)\n31- return self._put(path, data={'fail_libraries_policy': fail_libraries_policy, 'type': scoring_type})\n--\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-850d467db2015a45",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Good. Now I can build a small, runnable example: a Python CLI script that uses the official Contrast Python SDK (ContrastSdk) to pull org application inventory + vulnerability (trace) summary, driven by env vars, with a --mock offline mode so it runs without credentials. That's light: no local stacks, no long-running commands.
Blocked aspect: live calls need Contrast TeamServer credentials (username, API key, service key) and an org UUID — which I don't have. That's an honest limitation, not a full stop, since there is a real developer workflow (SDK + REST API). I'll say plainly I'm blocked from live verification.
Pricing: not public. Contrast is enterprise/quote-based: annual subscription, priced per module (Assess IAST/SAST, Protect RASP, Assess Lite, Library/SCA...) and by number of protected applications, with tiered volume discounts; enterprise/self-managed TeamServer hosting affects price. There's no public price list, free tier, or self-serve signup (trial via sales). I should note that I can't verify exact figures without docs access — do I have network to fetch pricing page? Network works. Let me try fetching contrastsecurity.com pricing page briefly to ground it.
cd /tmp && curl -sL -m 12 -A "Mozilla/5.0" https://www.contrastsecurity.com/pricing -o p.html -w "%{http_code}\n"; wc -c p.html; python - <<'EOF'
import re,html
t=open('/tmp/p.html',encoding='utf-8',errors='ignore').read()
t=re.sub(r'<(script|style).*?</\1>','',t,flags=re.S)
txt=re.sub(r'\s+',' ',html.unescape(re.sub(r'<[^>]+>',' ',t)))
print(txt[:1800])
EOF200
200 68060 p.html Contrast Security Pricing and Packaging | Application Security Pricing | Contrast Security Login Contrast customer Contrast partner Contact us Products Contrast runtime security platform The next evolution in application security Explore platform Contrast ADR Detect and respond to attacks Contrast CVE Shield Stop CVE exploitation in real time Contrast AST Find and fix vulnerabilities Assess SCA Scan Solutions Integrations Languages and frameworks Contrast One Pricing Partner Partners Partner program overview Explore the program Channel partners GSI and service providers Technology partners Become a partner Find a partner Visit partner portal Gartner ® Report: Mythos Changed the Conversation Get the report Customers Company Company About us Leadership team Careers Press releases Contact us Latest press Jul 29, 2026 Contrast Security launches Contrast CVE Shield as AI accelerates exploitation of known vulnerabilities With a free tier for runtime visibility, CVE Shield blocks Mythos-level attacks inside production apps and APIs before a patch lands. Read the release Resources Resource center Analyst reports, eBooks, on-demand webinars, white papers and more. Visit resource center Support and services Documentation Product release notes Blog Education Events Glossary OWASP Top 10 Mythos AI Exploits Guide A DevSecOps buyer's guide for application security Get the guide Contact us Login Contrast customer Contrast partner Contact us Search Try Contrast Try Contrast × Pricing Contrast CVE Shield Your Mythos shield to detect, monitor and prevent exploitation of AI-accelerated attacks FREE $0 No credit card required CVE Shield exploitability monitoring on two applications. Start free → 2 applications (unlimited hosts)* *Usage limits apply CVE Shield runtime monitoring: CVE explo
Recorded tool call
{
"type": "tool_call",
"seq": 42,
"ts": "2026-10-06T15:00:19.240Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && curl -sL -m 12 -A \"Mozilla/5.0\" https://www.contrastsecurity.com/pricing -o p.html -w \"%{http_code}\\n\"; wc -c p.html; python - <<'EOF'\nimport re,html\nt=open('/tmp/p.html',encoding='utf-8',errors='ignore').read()\nt=re.sub(r'<(script|style).*?</\\1>','',t,flags=re.S)\ntxt=re.sub(r'\\s+',' ',html.unescape(re.sub(r'<[^>]+>',' ',t)))\nprint(txt[:1800])\nEOF"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-81453911dc5e2f7b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 44,
"ts": "2026-10-06T15:00:20.126Z",
"turn": 1,
"output": {
"content": [
{
"text": "200\n68060 p.html\n Contrast Security Pricing and Packaging | Application Security Pricing | Contrast Security Login Contrast customer Contrast partner Contact us Products Contrast runtime security platform The next evolution in application security Explore platform Contrast ADR Detect and respond to attacks Contrast CVE Shield Stop CVE exploitation in real time Contrast AST Find and fix vulnerabilities Assess SCA Scan Solutions Integrations Languages and frameworks Contrast One Pricing Partner Partners Partner program overview Explore the program Channel partners GSI and service providers Technology partners Become a partner Find a partner Visit partner portal Gartner ® Report: Mythos Changed the Conversation Get the report Customers Company Company About us Leadership team Careers Press releases Contact us Latest press Jul 29, 2026 Contrast Security launches Contrast CVE Shield as AI accelerates exploitation of known vulnerabilities With a free tier for runtime visibility, CVE Shield blocks Mythos-level attacks inside production apps and APIs before a patch lands. Read the release Resources Resource center Analyst reports, eBooks, on-demand webinars, white papers and more. Visit resource center Support and services Documentation Product release notes Blog Education Events Glossary OWASP Top 10 Mythos AI Exploits Guide A DevSecOps buyer's guide for application security Get the guide Contact us Login Contrast customer Contrast partner Contact us Search Try Contrast Try Contrast × Pricing Contrast CVE Shield Your Mythos shield to detect, monitor and prevent exploitation of AI-accelerated attacks FREE $0 No credit card required CVE Shield exploitability monitoring on two applications. Start free → 2 applications (unlimited hosts)* *Usage limits apply CVE Shield runtime monitoring: CVE explo\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-81453911dc5e2f7b",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Let's get more of the pricing page text to ground the pricing answer.
cd /tmp && python - <<'EOF'
import re,html
t=open('/tmp/p.html',encoding='utf-8',errors='ignore').read()
t=re.sub(r'<(script|style).*?</\1>','',t,flags=re.S)
txt=re.sub(r'\s+',' ',html.unescape(re.sub(r'<[^>]+>',' ',t)))
print(txt[1700:6000])
EOFree → 2 applications (unlimited hosts)* *Usage limits apply CVE Shield runtime monitoring: CVE exploitation detection and reachability Runtime SCA with dependen
ree → 2 applications (unlimited hosts)* *Usage limits apply CVE Shield runtime monitoring: CVE exploitation detection and reachability Runtime SCA with dependency tree Dynamic scoring to prioritize fixes 14-day rolling history Single user Community support For Java apps running on Linux. Broader platform support coming soon. PRO (to be released soon) $750 / month Billed annually Full runtime blocking and monitoring for growing teams. Start pro → 8 applications (unlimited hosts)* *Usage limits apply CVE Shield active blocking + runtime monitoring Block CVE exploitation at runtime before the fix ships Runtime SCA with dependency tree Dynamic scoring to prioritize fixes 90-day rolling history Up to 5 users Email support Enterprise Talk to sales for a quote Enterprise-scale coverage with SLA guarantees and dedicated support. Talk to sales → Full ADR production monitoring and blocking for CVEs and custom code vulnerabilities SIEM and ticketing integrations Compliance reporting Enterprise access controls: RBAC, SAML SSO 1-year rolling history Unlimited users Standard support Frequently asked questions Will this just add more noise to my backlog? No, the opposite. AI-accelerated attacks are surfacing and weaponizing CVEs faster than any team can triage. CVE Shield works at runtime, inside the live application, so it shows you which CVEs an attacker can actually reach and exploit rather than every flaw that exists in your code. Your team spends its time on what's genuinely exploitable, and CVE Shield blocks attempts before a patch ships. How does CVE Shield prevent AI-accelerated attacks? CVE Shield provides runtime protection by identifying which CVEs are reachable and exploitable within your live application. By monitoring execution in real-time, it allows your team to block attack attempts before a patch is deployed, effectively reducing the window of vulnerability. What does the Free tier actually include? The Free tier is a functional starting point, not a trial. You can monitor up to 2 applications in production, see real exploitation attempts as they happen, and keep a 14-day rolling history, all in observation mode. Upgrade when your team needs blocking, more applications, or SIEM and compliance integrations. What counts as an application? We describe plans in applications and meter them in services. A service is an independently deployable runtime component that handles a specific business capability and forms part of an application (e.g., billing, shipping). A typical application is made up of several services. Free covers about 2 applications, up to 12 services. Pro covers about 8 applications, up to 50 services. What are the Usage Limits? Plans are metered by services. For Free, you get up to 12 services across 2 applications. For Pro, up to 50 services across 8 applications. What's the difference between observation and blocking? Observation mode (available in the Free tier) provides visibility into exploitation attempts, including the CVE, route, and source IP. Blocking mode (Pro and Enterprise) stops these attempts at runtime, preventing potential breaches before they succeed. How does billing work? Free covers up to 2 applications at no cost and needs no credit card. Pro is a flat annual price, starting at $750/month billed annually ($9,000/year), and will be available soon. Enterprise moves to annual consumption pricing based on production hosts with a custom quote from sales. How are plans metered in CVE Shield? Plans are metered by the number of services. A service is an independently deployable runtime component. The Free tier includes up to 12 services across 2 applications, while the Pro tier offers up to 50 services across 8 applications. Can I change plans as my needs grow? Yes. You can start on Free and upgrade to Pro when it becomes available, and coverage for new applications takes effect once the agent is reporting. Enterprise is handled with our sales team. Does CVE Shield require code changes to install? No. CVE Shield uses the Contrast ADR agent, which is installed once. It provides automated protection for all covered CVEs without requiring manual code changes or application restarts after the initial setup. Which programming languages does CVE Shield support? At launch, CVE Shield supports Java a
Recorded tool call
{
"type": "tool_call",
"seq": 47,
"ts": "2026-10-06T15:00:22.567Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp && python - <<'EOF'\nimport re,html\nt=open('/tmp/p.html',encoding='utf-8',errors='ignore').read()\nt=re.sub(r'<(script|style).*?</\\1>','',t,flags=re.S)\ntxt=re.sub(r'\\s+',' ',html.unescape(re.sub(r'<[^>]+>',' ',t)))\nprint(txt[1700:6000])\nEOF"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b4e2c4edf1b3ac2f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 48,
"ts": "2026-10-06T15:00:22.567Z",
"turn": 1,
"output": {
"content": [
{
"text": "ree → 2 applications (unlimited hosts)* *Usage limits apply CVE Shield runtime monitoring: CVE exploitation detection and reachability Runtime SCA with dependency tree Dynamic scoring to prioritize fixes 14-day rolling history Single user Community support For Java apps running on Linux. Broader platform support coming soon. PRO (to be released soon) $750 / month Billed annually Full runtime blocking and monitoring for growing teams. Start pro → 8 applications (unlimited hosts)* *Usage limits apply CVE Shield active blocking + runtime monitoring Block CVE exploitation at runtime before the fix ships Runtime SCA with dependency tree Dynamic scoring to prioritize fixes 90-day rolling history Up to 5 users Email support Enterprise Talk to sales for a quote Enterprise-scale coverage with SLA guarantees and dedicated support. Talk to sales → Full ADR production monitoring and blocking for CVEs and custom code vulnerabilities SIEM and ticketing integrations Compliance reporting Enterprise access controls: RBAC, SAML SSO 1-year rolling history Unlimited users Standard support Frequently asked questions Will this just add more noise to my backlog? No, the opposite. AI-accelerated attacks are surfacing and weaponizing CVEs faster than any team can triage. CVE Shield works at runtime, inside the live application, so it shows you which CVEs an attacker can actually reach and exploit rather than every flaw that exists in your code. Your team spends its time on what's genuinely exploitable, and CVE Shield blocks attempts before a patch ships. How does CVE Shield prevent AI-accelerated attacks? CVE Shield provides runtime protection by identifying which CVEs are reachable and exploitable within your live application. By monitoring execution in real-time, it allows your team to block attack attempts before a patch is deployed, effectively reducing the window of vulnerability. What does the Free tier actually include? The Free tier is a functional starting point, not a trial. You can monitor up to 2 applications in production, see real exploitation attempts as they happen, and keep a 14-day rolling history, all in observation mode. Upgrade when your team needs blocking, more applications, or SIEM and compliance integrations. What counts as an application? We describe plans in applications and meter them in services. A service is an independently deployable runtime component that handles a specific business capability and forms part of an application (e.g., billing, shipping). A typical application is made up of several services. Free covers about 2 applications, up to 12 services. Pro covers about 8 applications, up to 50 services. What are the Usage Limits? Plans are metered by services. For Free, you get up to 12 services across 2 applications. For Pro, up to 50 services across 8 applications. What's the difference between observation and blocking? Observation mode (available in the Free tier) provides visibility into exploitation attempts, including the CVE, route, and source IP. Blocking mode (Pro and Enterprise) stops these attempts at runtime, preventing potential breaches before they succeed. How does billing work? Free covers up to 2 applications at no cost and needs no credit card. Pro is a flat annual price, starting at $750/month billed annually ($9,000/year), and will be available soon. Enterprise moves to annual consumption pricing based on production hosts with a custom quote from sales. How are plans metered in CVE Shield? Plans are metered by the number of services. A service is an independently deployable runtime component. The Free tier includes up to 12 services across 2 applications, while the Pro tier offers up to 50 services across 8 applications. Can I change plans as my needs grow? Yes. You can start on Free and upgrade to Pro when it becomes available, and coverage for new applications takes effect once the agent is reporting. Enterprise is handled with our sales team. Does CVE Shield require code changes to install? No. CVE Shield uses the Contrast ADR agent, which is installed once. It provides automated protection for all covered CVEs without requiring manual code changes or application restarts after the initial setup. Which programming languages does CVE Shield support? At launch, CVE Shield supports Java a\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b4e2c4edf1b3ac2f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Excellent — real, grounded pricing from contrastsecurity.com/pricing (fetched live):
- CVE Shield FREE: $0, 2 applications (up to 12 services), observation mode, 14-day history, 1 user, community support, Java on Linux.
- PRO: $750/month billed annually ($9,000/year), "to be released soon", 8 applications (up to 50 services), blocking + monitoring, 90-day history, up to 5 users, email support.
- Enterprise: quote from sales, annual consumption pricing based on production hosts, full ADR, SIEM/ticketing, RBAC/SAML, 1-year history, unlimited users.
Note: that pricing page is for CVE Shield specifically; the broader Contrast AST/ADR platform pricing is quote-based (Enterprise). Good.
Now build the example. Light: Python script using official SDK contrast-security (ContrastSdk). Include:
- requirements.txt
- example script that: reads CONTRAST_USERNAME, CONTRAST_API_KEY, CONTRAST_SERVICE_KEY, CONTRAST_ORG_UUID (or auto-discovers org via get_organizations), lists apps, filters traces (vulns), prints summary table; supports --mock for offline demo.
- README with pricing + credentials note.
Check org listing method names and trace filter fields to make it correct.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -n "def get_organizations\|def get_visible_organizations\|def get_" user_api/organization_api.py | head -20; echo ---FILTERS---; sed -n 1,60p filters/application_trace_filter.py; echo ---APPF---; sed -n 1,45p filters/application_filter.py
13: def get_organization_info(self, org_uuid, expand=None):
13: def get_organization_info(self, org_uuid, expand=None):
17: def get_organization_administrators(self, org_uuid):
21: def get_organization_application_roles(self, org_uuid):
25: def get_organization_library_scoring(self, org_uuid):
33: def get_organization_servers_needing_restart(self, org_uuid, language):
37: def get_organization_application_stats(self, org_uuid, interval='WEEK', expand=None):
41: def get_organization_library_stats(self, org_uuid, interval='WEEK', expand=None):
45: def get_organization_server_stats(self, org_uuid, interval='WEEK', expand=None):
49: def get_organization_trace_stats(self, org_uuid, interval='WEEK'):
53: def get_organization_server_settings(self, org_uuid):
---FILTERS---
class ApplicationTraceFilter(object):
ExpandCard = 'card'
ExpandEvents = 'events'
ExpandNotes = 'notes'
ExpandRequest = 'request'
ExpandApplication = 'application'
ExpandServers = 'servers'
TimestampFilterFirst = 'FIRST'
TimestampFilterLast = 'LAST'
def __init__(self):
self.filter_text = None
self.start_date = None
self.end_date = None
self.filter_tags = []
self.severities = []
self.statuses = []
self.substatus = []
self.vuln_types = []
self.app_version_tags = []
self.servers = []
self.environments = []
self.servers = []
self.urls = []
self.sinks = []
self.sinks_values = []
self.routes = []
self.modules = []
self.app_tags = []
self.app_id = None
self.quick_filter = "ALL"
self.security_standards = []
self.tracked = True
self.untracked = True
self.timestamp_filter = None
self.match_route_path_params = True
self.metadata_filters = []
self.licensed_only = True
self.expand = []
self.limit = 20
self.offset = 0
self.sort = '-lastTimeSeen'
def get_body_params_as_json(self):
return {
'appVersionTags': self.app_version_tags,
'applicationID': self.app_id,
'applicationTags': self.app_tags,
'startDate': self.start_date,
'endDate': self.end_date,
'environments': self.environments,
'filterTags': self.filter_tags,
'filterText': self.filter_text,
'licensedOnly': self.licensed_only,
'matchRoutePathParams': self.match_route_path_params,
'metadataFilters': self.metadata_filters,
'modules': self.modules,
'quickFilter': self.quick_filter,
'routes': self.routes,
---APPF---
import contrast_security.filters.filter_utils as utils
class ApplicationFilter(object):
ExpandScores = 'scores'
ExpandTraceBreakdown = 'trace_breakdown'
ExpandLicense = 'license'
ExpandTechnologies = 'technologies'
def __init__(self):
self.filter_text = None
self.filter_appcode = None
self.filter_servers = []
self.filter_techs = []
self.filter_tags = []
self.filter_languages = []
self.filter_compliance = []
self.environment = []
self.app_importances = []
self.filter_vulnerabilities_severities = []
self.include_archived = False
self.include_only_license = False
self.quick_filter = "ALL"
self.metadata_filters = []
self.expand = []
self.include_merged = False
self.limit = 20
self.offset = 0
self.sort = '-appName'
def get_body_params_as_json(self):
return {
'filterText': utils.parse_single_element_list_to_string(self.filter_text),
'filterAppCode': self.filter_appcode,
'filterServers': self.filter_servers,
'filterTechs': self.filter_techs,
'filterTags': self.filter_tags,
'filterLanguages': self.filter_languages,
'filterCompliance': self.filter_compliance,
'environment': self.environment,
'appImportances': self.app_importances,
'filterVulnSeverities': self.filter_vulnerabilities_severities,
'includeArchived': self.include_archived,
'includeOnlyLicensed': self.include_only_license,
'quickFilter': self.quick_filter,
Recorded tool call
{
"type": "tool_call",
"seq": 51,
"ts": "2026-10-06T15:00:27.957Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -n \"def get_organizations\\|def get_visible_organizations\\|def get_\" user_api/organization_api.py | head -20; echo ---FILTERS---; sed -n 1,60p filters/application_trace_filter.py; echo ---APPF---; sed -n 1,45p filters/application_filter.py"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-876f72a29b85631f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 52,
"ts": "2026-10-06T15:00:27.957Z",
"turn": 1,
"output": {
"content": [
{
"text": "13: def get_organization_info(self, org_uuid, expand=None):\n17: def get_organization_administrators(self, org_uuid):\n21: def get_organization_application_roles(self, org_uuid):\n25: def get_organization_library_scoring(self, org_uuid):\n33: def get_organization_servers_needing_restart(self, org_uuid, language):\n37: def get_organization_application_stats(self, org_uuid, interval='WEEK', expand=None):\n41: def get_organization_library_stats(self, org_uuid, interval='WEEK', expand=None):\n45: def get_organization_server_stats(self, org_uuid, interval='WEEK', expand=None):\n49: def get_organization_trace_stats(self, org_uuid, interval='WEEK'):\n53: def get_organization_server_settings(self, org_uuid):\n---FILTERS---\nclass ApplicationTraceFilter(object):\n ExpandCard = 'card'\n ExpandEvents = 'events'\n ExpandNotes = 'notes'\n ExpandRequest = 'request'\n ExpandApplication = 'application'\n ExpandServers = 'servers'\n\n TimestampFilterFirst = 'FIRST'\n TimestampFilterLast = 'LAST'\n\n def __init__(self):\n self.filter_text = None\n self.start_date = None\n self.end_date = None\n self.filter_tags = []\n self.severities = []\n self.statuses = []\n self.substatus = []\n self.vuln_types = []\n self.app_version_tags = []\n self.servers = []\n self.environments = []\n self.servers = []\n self.urls = []\n self.sinks = []\n self.sinks_values = []\n self.routes = []\n self.modules = []\n self.app_tags = []\n self.app_id = None\n self.quick_filter = \"ALL\"\n self.security_standards = []\n self.tracked = True\n self.untracked = True\n self.timestamp_filter = None\n self.match_route_path_params = True\n self.metadata_filters = []\n self.licensed_only = True\n self.expand = []\n self.limit = 20\n self.offset = 0\n self.sort = '-lastTimeSeen'\n\n def get_body_params_as_json(self):\n return {\n 'appVersionTags': self.app_version_tags,\n 'applicationID': self.app_id,\n 'applicationTags': self.app_tags,\n 'startDate': self.start_date,\n 'endDate': self.end_date,\n 'environments': self.environments,\n 'filterTags': self.filter_tags,\n 'filterText': self.filter_text,\n 'licensedOnly': self.licensed_only,\n 'matchRoutePathParams': self.match_route_path_params,\n 'metadataFilters': self.metadata_filters,\n 'modules': self.modules,\n 'quickFilter': self.quick_filter,\n 'routes': self.routes,\n---APPF---\nimport contrast_security.filters.filter_utils as utils\n\n\nclass ApplicationFilter(object):\n ExpandScores = 'scores'\n ExpandTraceBreakdown = 'trace_breakdown'\n ExpandLicense = 'license'\n ExpandTechnologies = 'technologies'\n\n def __init__(self):\n self.filter_text = None\n self.filter_appcode = None\n self.filter_servers = []\n self.filter_techs = []\n self.filter_tags = []\n self.filter_languages = []\n self.filter_compliance = []\n self.environment = []\n self.app_importances = []\n self.filter_vulnerabilities_severities = []\n self.include_archived = False\n self.include_only_license = False\n self.quick_filter = \"ALL\"\n self.metadata_filters = []\n self.expand = []\n self.include_merged = False\n self.limit = 20\n self.offset = 0\n self.sort = '-appName'\n\n def get_body_params_as_json(self):\n return {\n 'filterText': utils.parse_single_element_list_to_string(self.filter_text),\n 'filterAppCode': self.filter_appcode,\n 'filterServers': self.filter_servers,\n 'filterTechs': self.filter_techs,\n 'filterTags': self.filter_tags,\n 'filterLanguages': self.filter_languages,\n 'filterCompliance': self.filter_compliance,\n 'environment': self.environment,\n 'appImportances': self.app_importances,\n 'filterVulnSeverities': self.filter_vulnerabilities_severities,\n 'includeArchived': self.include_archived,\n 'includeOnlyLicensed': self.include_only_license,\n 'quickFilter': self.quick_filter,\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-876f72a29b85631f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
How does one list orgs? Check profile_api for get_user_organizations or user_api.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -rn "def get_.*org" user_api/*.py | head; echo ---; grep -n "organizations" contrast_sdk.py | head; echo ---; grep -rn "def get_" user_api/profile_api.py
user_api/alerts_api.py:9: def get_alerts(self, org_uuid):
user_api/alerts_api.py:9: def get_alerts(self, org_uuid): user_api/alerts_api.py:13: def get_alert_data(self, org_uuid, alert_id): user_api/application_api.py:12: def get_inactive_applications(self, org_uuid, expand=None, include_archived=False, include_merged=False, limit=5): user_api/application_api.py:16: def get_newest_applications(self, org_uuid, expand=None, include_archived=False, include_merged=False, limit=5): user_api/application_api.py:20: def get_recent_applications(self, org_uuid, expand=None, include_archived=False, include_merged=False, limit=5): user_api/application_api.py:25: def get_application_agent_activity(self, org_uuid, app_id, range, include_merged=False): user_api/application_api.py:30: def get_application_components(self,org_uuid, app_id): user_api/application_api.py:35: def get_application_coverage(self,org_uuid, app_id, include_merged=True, limit=50): user_api/application_api.py:39: def get_application_coverage_past_week(self,org_uuid, app_id, include_merged=True, limit=50): user_api/application_api.py:44: def get_application_history(self, org_uuid, app_id, include_merged=True): --- 235: self.get_profile_organizations = self._profile.get_profile_organizations --- 9: def get_profile_info(self, expand=None): 12: def get_profile_organizations(self): 15: def get_profile_default_organization(self): 18: def get_org_info(self, org_uuid): 22: def get_profile_password_policy(self): 25: def get_profile_roles(self):
Recorded tool call
{
"type": "tool_call",
"seq": 55,
"ts": "2026-10-06T15:00:30.296Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -rn \"def get_.*org\" user_api/*.py | head; echo ---; grep -n \"organizations\" contrast_sdk.py | head; echo ---; grep -rn \"def get_\" user_api/profile_api.py"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-823947d7bc3116d2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 56,
"ts": "2026-10-06T15:00:30.296Z",
"turn": 1,
"output": {
"content": [
{
"text": "user_api/alerts_api.py:9: def get_alerts(self, org_uuid):\nuser_api/alerts_api.py:13: def get_alert_data(self, org_uuid, alert_id):\nuser_api/application_api.py:12: def get_inactive_applications(self, org_uuid, expand=None, include_archived=False, include_merged=False, limit=5):\nuser_api/application_api.py:16: def get_newest_applications(self, org_uuid, expand=None, include_archived=False, include_merged=False, limit=5):\nuser_api/application_api.py:20: def get_recent_applications(self, org_uuid, expand=None, include_archived=False, include_merged=False, limit=5):\nuser_api/application_api.py:25: def get_application_agent_activity(self, org_uuid, app_id, range, include_merged=False):\nuser_api/application_api.py:30: def get_application_components(self,org_uuid, app_id):\nuser_api/application_api.py:35: def get_application_coverage(self,org_uuid, app_id, include_merged=True, limit=50):\nuser_api/application_api.py:39: def get_application_coverage_past_week(self,org_uuid, app_id, include_merged=True, limit=50):\nuser_api/application_api.py:44: def get_application_history(self, org_uuid, app_id, include_merged=True):\n---\n235: self.get_profile_organizations = self._profile.get_profile_organizations\n---\n9: def get_profile_info(self, expand=None):\n12: def get_profile_organizations(self):\n15: def get_profile_default_organization(self):\n18: def get_org_info(self, org_uuid):\n22: def get_profile_password_policy(self):\n25: def get_profile_roles(self):\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-823947d7bc3116d2",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Good: get_profile_organizations() and get_profile_default_organization() for org discovery.
Now write the example. Files in /sandbox/repo:
- requirements.txt
- contrast_inventory.py (CLI)
- README.md
- .env.example
- mock data for --mock mode (embedded).
Keep it small. Write script.
Network, PyPI, and Contrast's live docs/pricing page are all reachable, and Contrast ships an official Python SDK — so there is a real developer workflow here. Building it now.
File edits
1 file
Writtencontrast_inventory.pyView changes
/sandbox/repo/contrast_inventory.py
Written content · previous contents were not recorded
#!/usr/bin/env python3"""Minimal Contrast Security example: org -> applications -> vulnerabilities ("traces").Uses the official Contrast Python SDK (PyPI: contrast-security) against the hostedTeamServer API (https://app.contrastsecurity.com/Contrast/api). No local services.Credentials come from the environment (never hard-code them):CONTRAST_USERNAME Contrast login emailCONTRAST_API_KEY API key (Your Account -> API Keys)CONTRAST_SERVICE_KEY Service key (Your Account -> API Keys)CONTRAST_ORG_UUID optional; auto-discovered from your profile if unsetCONTRAST_URL optional; defaults to the hosted SaaS TeamServerRun offline against fixture data (no credentials needed):python contrast_inventory.py --mock"""from __future__ import annotationsimport argparseimport jsonimport osimport sysfrom collections import Countertry:from contrast_security import ContrastSdkfrom contrast_security.filters import ApplicationFilter, ApplicationTraceFilterexcept ImportError: # pragma: no coversys.exit("Missing SDK. Install it with: pip install -r requirements.txt")SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "NOTE"]# Fixture used by --mock so the example is runnable without an account.MOCK = {"applications": [{"app_id": "app-billing", "name": "billing-service", "language": "JAVA","importance": "CRITICAL", "last_sha1": "abc123"},{"app_id": "app-store", "name": "storefront", "language": "JAVASCRIPT","importance": "HIGH", "last_sha1": "def456"},],"traces": {"app-billing": [{"uuid": "t-1", "title": "SQL Injection", "severity": "CRITICAL","status": "REPORTED", "rule_name": "sql-injection", "route": "/api/invoices"},{"uuid": "t-2", "title": "Path Traversal", "severity": "HIGH","status": "REPORTED", "rule_name": "path-traversal", "route": "/api/export"},],"app-store": [{"uuid": "t-3", "title": "Cross-Site Scripting (Reflected)", "severity": "MEDIUM","status": "REMEDIATED", "rule_name": "xss-reflected", "route": "/search"},],},}def env(name: str, required: bool = True) -> str:value = os.environ.get(name, "").strip()if not value and required:sys.exit(f"{name} is not set. Export it, or run with --mock for an offline demo.")return valuedef build_sdk():return ContrastSdk(username=env("CONTRAST_USERNAME"),api_key=env("CONTRAST_API_KEY"),service_key=env("CONTRAST_SERVICE_KEY"),teamserver_url=os.environ.get("CONTRAST_URL", "https://app.contrastsecurity.com"),)def resolve_org(sdk) -> str:org = env("CONTRAST_ORG_UUID", required=False)if org:return orgdefault = sdk.get_profile_default_organization()org = (default or {}).get("organization_uuid")if not org:orgs = (sdk.get_profile_organizations() or {}).get("organizations", [])org = orgs[0]["organization_uuid"] if orgs else Noneif not org:sys.exit("No organization visible to this user; set CONTRAST_ORG_UUID explicitly.")return orgdef fetch_live(sdk, org: str, limit: int):app_filter = ApplicationFilter()app_filter.limit = limitapp_filter.expand = [ApplicationFilter.ExpandTraceBreakdown]apps = (sdk.filter_applications(org, app_filter) or {}).get("applications", [])per_app = {}for app in apps:trace_filter = ApplicationTraceFilter()trace_filter.app_id = app["app_id"]trace_filter.limit = limittrace_filter.quick_filter = "OPEN"per_app[app["app_id"]] = (sdk.filter_application_traces(org, app["app_id"], trace_filter) or {}).get("traces", [])return org, apps, per_appdef render(org, apps, per_app, raw: bool) -> None:if raw:print(json.dumps({"org": org, "applications": apps, "traces": per_app}, indent=2, default=str))returnprint(f"Organization: {org}")print(f"Applications: {len(apps)}\n")severities: Counter[str] = Counter()for app in apps:traces = per_app.get(app["app_id"], [])print(f" {app.get('name')} ({app.get('app_id')}, {app.get('language')}, "f"importance={app.get('importance')}) -> {len(traces)} open findings")for trace in sorted(traces, key=lambda t: SEVERITY_ORDER.index(t.get("severity", "NOTE"))if t.get("severity") in SEVERITY_ORDER else 99):severities[trace.get("severity", "UNKNOWN")] += 1print(f" [{trace.get('severity'):<8}] {trace.get('title')} "f"route={trace.get('route')} status={trace.get('status')} uuid={trace.get('uuid')}")print("\nOpen findings by severity:")for sev in SEVERITY_ORDER:if severities.get(sev):print(f" {sev:<9} {severities[sev]}")if not severities:print(" none")def main() -> None:parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)parser.add_argument("--mock", action="store_true", help="run offline against fixture data")parser.add_argument("--limit", type=int, default=25, help="page size per API call")parser.add_argument("--json", action="store_true", help="print raw JSON instead of a summary")args = parser.parse_args()if args.mock:render("mock-org-uuid", MOCK["applications"], MOCK["traces"], args.json)returnsdk = build_sdk()org = resolve_org(sdk)apps, per_app = fetch_live(sdk, org, args.limit)[1:]render(org, apps, per_app, args.json)if __name__ == "__main__":main()
Source: write · chatcmpl-tool-81696a4af43351da
Recorded tool call
{
"type": "tool_call",
"seq": 60,
"ts": "2026-10-06T15:00:47.408Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/contrast_inventory.py",
"content": "#!/usr/bin/env python3\n\"\"\"Minimal Contrast Security example: org -> applications -> vulnerabilities (\"traces\").\n\nUses the official Contrast Python SDK (PyPI: contrast-security) against the hosted\nTeamServer API (https://app.contrastsecurity.com/Contrast/api). No local services.\n\nCredentials come from the environment (never hard-code them):\n CONTRAST_USERNAME Contrast login email\n CONTRAST_API_KEY API key (Your Account -> API Keys)\n CONTRAST_SERVICE_KEY Service key (Your Account -> API Keys)\n CONTRAST_ORG_UUID optional; auto-discovered from your profile if unset\n CONTRAST_URL optional; defaults to the hosted SaaS TeamServer\n\nRun offline against fixture data (no credentials needed):\n python contrast_inventory.py --mock\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport sys\nfrom collections import Counter\n\ntry:\n from contrast_security import ContrastSdk\n from contrast_security.filters import ApplicationFilter, ApplicationTraceFilter\nexcept ImportError: # pragma: no cover\n sys.exit(\"Missing SDK. Install it with: pip install -r requirements.txt\")\n\nSEVERITY_ORDER = [\"CRITICAL\", \"HIGH\", \"MEDIUM\", \"LOW\", \"NOTE\"]\n\n# Fixture used by --mock so the example is runnable without an account.\nMOCK = {\n \"applications\": [\n {\"app_id\": \"app-billing\", \"name\": \"billing-service\", \"language\": \"JAVA\",\n \"importance\": \"CRITICAL\", \"last_sha1\": \"abc123\"},\n {\"app_id\": \"app-store\", \"name\": \"storefront\", \"language\": \"JAVASCRIPT\",\n \"importance\": \"HIGH\", \"last_sha1\": \"def456\"},\n ],\n \"traces\": {\n \"app-billing\": [\n {\"uuid\": \"t-1\", \"title\": \"SQL Injection\", \"severity\": \"CRITICAL\",\n \"status\": \"REPORTED\", \"rule_name\": \"sql-injection\", \"route\": \"/api/invoices\"},\n {\"uuid\": \"t-2\", \"title\": \"Path Traversal\", \"severity\": \"HIGH\",\n \"status\": \"REPORTED\", \"rule_name\": \"path-traversal\", \"route\": \"/api/export\"},\n ],\n \"app-store\": [\n {\"uuid\": \"t-3\", \"title\": \"Cross-Site Scripting (Reflected)\", \"severity\": \"MEDIUM\",\n \"status\": \"REMEDIATED\", \"rule_name\": \"xss-reflected\", \"route\": \"/search\"},\n ],\n },\n}\n\n\ndef env(name: str, required: bool = True) -> str:\n value = os.environ.get(name, \"\").strip()\n if not value and required:\n sys.exit(f\"{name} is not set. Export it, or run with --mock for an offline demo.\")\n return value\n\n\ndef build_sdk():\n return ContrastSdk(\n username=env(\"CONTRAST_USERNAME\"),\n api_key=env(\"CONTRAST_API_KEY\"),\n service_key=env(\"CONTRAST_SERVICE_KEY\"),\n teamserver_url=os.environ.get(\"CONTRAST_URL\", \"https://app.contrastsecurity.com\"),\n )\n\n\ndef resolve_org(sdk) -> str:\n org = env(\"CONTRAST_ORG_UUID\", required=False)\n if org:\n return org\n default = sdk.get_profile_default_organization()\n org = (default or {}).get(\"organization_uuid\")\n if not org:\n orgs = (sdk.get_profile_organizations() or {}).get(\"organizations\", [])\n org = orgs[0][\"organization_uuid\"] if orgs else None\n if not org:\n sys.exit(\"No organization visible to this user; set CONTRAST_ORG_UUID explicitly.\")\n return org\n\n\ndef fetch_live(sdk, org: str, limit: int):\n app_filter = ApplicationFilter()\n app_filter.limit = limit\n app_filter.expand = [ApplicationFilter.ExpandTraceBreakdown]\n apps = (sdk.filter_applications(org, app_filter) or {}).get(\"applications\", [])\n\n per_app = {}\n for app in apps:\n trace_filter = ApplicationTraceFilter()\n trace_filter.app_id = app[\"app_id\"]\n trace_filter.limit = limit\n trace_filter.quick_filter = \"OPEN\"\n per_app[app[\"app_id\"]] = (sdk.filter_application_traces(org, app[\"app_id\"], trace_filter) or {}).get(\n \"traces\", []\n )\n return org, apps, per_app\n\n\ndef render(org, apps, per_app, raw: bool) -> None:\n if raw:\n print(json.dumps({\"org\": org, \"applications\": apps, \"traces\": per_app}, indent=2, default=str))\n return\n\n print(f\"Organization: {org}\")\n print(f\"Applications: {len(apps)}\\n\")\n severities: Counter[str] = Counter()\n\n for app in apps:\n traces = per_app.get(app[\"app_id\"], [])\n print(f\" {app.get('name')} ({app.get('app_id')}, {app.get('language')}, \"\n f\"importance={app.get('importance')}) -> {len(traces)} open findings\")\n for trace in sorted(traces, key=lambda t: SEVERITY_ORDER.index(t.get(\"severity\", \"NOTE\"))\n if t.get(\"severity\") in SEVERITY_ORDER else 99):\n severities[trace.get(\"severity\", \"UNKNOWN\")] += 1\n print(f\" [{trace.get('severity'):<8}] {trace.get('title')} \"\n f\"route={trace.get('route')} status={trace.get('status')} uuid={trace.get('uuid')}\")\n\n print(\"\\nOpen findings by severity:\")\n for sev in SEVERITY_ORDER:\n if severities.get(sev):\n print(f\" {sev:<9} {severities[sev]}\")\n if not severities:\n print(\" none\")\n\n\ndef main() -> None:\n parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)\n parser.add_argument(\"--mock\", action=\"store_true\", help=\"run offline against fixture data\")\n parser.add_argument(\"--limit\", type=int, default=25, help=\"page size per API call\")\n parser.add_argument(\"--json\", action=\"store_true\", help=\"print raw JSON instead of a summary\")\n args = parser.parse_args()\n\n if args.mock:\n render(\"mock-org-uuid\", MOCK[\"applications\"], MOCK[\"traces\"], args.json)\n return\n\n sdk = build_sdk()\n org = resolve_org(sdk)\n apps, per_app = fetch_live(sdk, org, args.limit)[1:]\n render(org, apps, per_app, args.json)\n\n\nif __name__ == \"__main__\":\n main()\n"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-81696a4af43351da",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 63,
"ts": "2026-10-06T15:00:47.408Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 5785 bytes to /sandbox/repo/contrast_inventory.py",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-81696a4af43351da",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtenrequirements.txtView changes
/sandbox/repo/requirements.txt
Written content · previous contents were not recorded
contrast-security>=0.23
Source: write · chatcmpl-tool-a0f8e6fe59408952
Recorded tool call
{
"type": "tool_call",
"seq": 61,
"ts": "2026-10-06T15:00:47.408Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/requirements.txt",
"content": "contrast-security>=0.23\n"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a0f8e6fe59408952",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 62,
"ts": "2026-10-06T15:00:47.408Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 24 bytes to /sandbox/repo/requirements.txt",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a0f8e6fe59408952",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Fix a subtle bug: apps, per_app = fetch_live(...)[1:] works but ugly. Let me clean it. Also run mock test.
Also check SDK exports ContrastSdk from package root — init.py was empty! from contrast_security import ContrastSdk would fail. Check: earlier cat __init__.py printed nothing before "---FILTERS---"? Actually output showed "---" then filters. So init.py is empty → must import from contrast_security.contrast_sdk import ContrastSdk. And filters: from contrast_security.filters import ... — check filters/init.py content.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && wc -c __init__.py filters/__init__.py && cat filters/__init__.py | head
0 __init__.py
0 __init__.py 0 filters/__init__.py 0 total
Recorded tool call
{
"type": "tool_call",
"seq": 66,
"ts": "2026-10-06T15:00:51.294Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && wc -c __init__.py filters/__init__.py && cat filters/__init__.py | head"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-bab89e2244821eb6",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 67,
"ts": "2026-10-06T15:00:51.294Z",
"turn": 1,
"output": {
"content": [
{
"text": "0 __init__.py\n0 filters/__init__.py\n0 total\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-bab89e2244821eb6",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Both empty. So imports must be explicit module paths: from contrast_security.contrast_sdk import ContrastSdk from contrast_security.filters.application_filter import ApplicationFilter from contrast_security.filters.application_trace_filter import ApplicationTraceFilter
Let me verify these work.
File edits
1 file
Modifiedcontrast_inventory.py+6−4View changes
/sandbox/repo/contrast_inventory.py
Replacement excerpt
try:- from contrast_security import ContrastSdk- from contrast_security.filters import ApplicationFilter, ApplicationTraceFilter+ # The SDK ships empty __init__.py files, so imports are fully qualified.+ from contrast_security.contrast_sdk import ContrastSdk+ from contrast_security.filters.application_filter import ApplicationFilter+ from contrast_security.filters.application_trace_filter import ApplicationTraceFilterexcept ImportError: # pragma: no cover
Replacement excerpt
sdk = build_sdk()org = resolve_org(sdk)- apps, per_app = fetch_live(sdk, org, args.limit)[1:]+ _, apps, per_app = fetch_live(sdk, org, args.limit)render(org, apps, per_app, args.json)
Replacement excerpt
- return org, apps, per_app+ return apps, per_app
Source: edit · chatcmpl-tool-b0cad6b62f12e3b0
Recorded tool call
{
"type": "tool_call",
"seq": 70,
"ts": "2026-10-06T15:00:55.612Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/contrast_inventory.py",
"edits": [
{
"newText": "try:\n # The SDK ships empty __init__.py files, so imports are fully qualified.\n from contrast_security.contrast_sdk import ContrastSdk\n from contrast_security.filters.application_filter import ApplicationFilter\n from contrast_security.filters.application_trace_filter import ApplicationTraceFilter\nexcept ImportError: # pragma: no cover",
"oldText": "try:\n from contrast_security import ContrastSdk\n from contrast_security.filters import ApplicationFilter, ApplicationTraceFilter\nexcept ImportError: # pragma: no cover"
},
{
"newText": " sdk = build_sdk()\n org = resolve_org(sdk)\n _, apps, per_app = fetch_live(sdk, org, args.limit)\n render(org, apps, per_app, args.json)",
"oldText": " sdk = build_sdk()\n org = resolve_org(sdk)\n apps, per_app = fetch_live(sdk, org, args.limit)[1:]\n render(org, apps, per_app, args.json)"
},
{
"newText": " return apps, per_app",
"oldText": " return org, apps, per_app"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b0cad6b62f12e3b0",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 71,
"ts": "2026-10-06T15:00:55.612Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 3 block(s) in /sandbox/repo/contrast_inventory.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 23 import sys\n 24 from collections import Counter\n 25 \n 26 try:\n- 27 from contrast_security import ContrastSdk\n- 28 from contrast_security.filters import ApplicationFilter, ApplicationTraceFilter\n+ 27 # The SDK ships empty __init__.py files, so imports are fully qualified.\n+ 28 from contrast_security.contrast_sdk import ContrastSdk\n+ 29 from contrast_security.filters.application_filter import ApplicationFilter\n+ 30 from contrast_security.filters.application_trace_filter import ApplicationTraceFilter\n 29 except ImportError: # pragma: no cover\n 30 sys.exit(\"Missing SDK. Install it with: pip install -r requirements.txt\")\n 31 \n 32 SEVERITY_ORDER = [\"CRITICAL\", \"HIGH\", \"MEDIUM\", \"LOW\", \"NOTE\"]\n ...\n 98 trace_filter.quick_filter = \"OPEN\"\n 99 per_app[app[\"app_id\"]] = (sdk.filter_application_traces(org, app[\"app_id\"], trace_filter) or {}).get(\n 100 \"traces\", []\n 101 )\n-102 return org, apps, per_app\n+104 return apps, per_app\n 103 \n 104 \n 105 def render(org, apps, per_app, raw: bool) -> None:\n 106 if raw:\n ...\n 141 return\n 142 \n 143 sdk = build_sdk()\n 144 org = resolve_org(sdk)\n-145 apps, per_app = fetch_live(sdk, org, args.limit)[1:]\n+147 _, apps, per_app = fetch_live(sdk, org, args.limit)\n 146 render(org, apps, per_app, args.json)\n 147 \n 148 \n 149 if __name__ == \"__main__\":\n ...",
"patch": "--- /sandbox/repo/contrast_inventory.py\n+++ /sandbox/repo/contrast_inventory.py\n@@ -23,10 +23,12 @@\n import sys\n from collections import Counter\n \n try:\n- from contrast_security import ContrastSdk\n- from contrast_security.filters import ApplicationFilter, ApplicationTraceFilter\n+ # The SDK ships empty __init__.py files, so imports are fully qualified.\n+ from contrast_security.contrast_sdk import ContrastSdk\n+ from contrast_security.filters.application_filter import ApplicationFilter\n+ from contrast_security.filters.application_trace_filter import ApplicationTraceFilter\n except ImportError: # pragma: no cover\n sys.exit(\"Missing SDK. Install it with: pip install -r requirements.txt\")\n \n SEVERITY_ORDER = [\"CRITICAL\", \"HIGH\", \"MEDIUM\", \"LOW\", \"NOTE\"]\n@@ -98,9 +100,9 @@\n trace_filter.quick_filter = \"OPEN\"\n per_app[app[\"app_id\"]] = (sdk.filter_application_traces(org, app[\"app_id\"], trace_filter) or {}).get(\n \"traces\", []\n )\n- return org, apps, per_app\n+ return apps, per_app\n \n \n def render(org, apps, per_app, raw: bool) -> None:\n if raw:\n@@ -141,9 +143,9 @@\n return\n \n sdk = build_sdk()\n org = resolve_org(sdk)\n- apps, per_app = fetch_live(sdk, org, args.limit)[1:]\n+ _, apps, per_app = fetch_live(sdk, org, args.limit)\n render(org, apps, per_app, args.json)\n \n \n if __name__ == \"__main__\":\n",
"firstChangedLine": 27
}
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b0cad6b62f12e3b0",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Wait, fetch_live now returns (apps, per_app), but main does _, apps, per_app = fetch_live(...). Fix: apps, per_app = fetch_live(sdk, org, args.limit).
File edits
1 file
Modifiedcontrast_inventory.py+1−1View changes
/sandbox/repo/contrast_inventory.py
Replacement excerpt
- _, apps, per_app = fetch_live(sdk, org, args.limit)+ apps, per_app = fetch_live(sdk, org, args.limit)
Source: edit · chatcmpl-tool-ab643bf9e3c3c146
Recorded tool call
{
"type": "tool_call",
"seq": 74,
"ts": "2026-10-06T15:00:58.813Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/contrast_inventory.py",
"edits": [
{
"newText": " apps, per_app = fetch_live(sdk, org, args.limit)",
"oldText": " _, apps, per_app = fetch_live(sdk, org, args.limit)"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-ab643bf9e3c3c146",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 75,
"ts": "2026-10-06T15:00:58.813Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/contrast_inventory.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 143 return\n 144 \n 145 sdk = build_sdk()\n 146 org = resolve_org(sdk)\n-147 _, apps, per_app = fetch_live(sdk, org, args.limit)\n+147 apps, per_app = fetch_live(sdk, org, args.limit)\n 148 render(org, apps, per_app, args.json)\n 149 \n 150 \n 151 if __name__ == \"__main__\":\n ...",
"patch": "--- /sandbox/repo/contrast_inventory.py\n+++ /sandbox/repo/contrast_inventory.py\n@@ -143,9 +143,9 @@\n return\n \n sdk = build_sdk()\n org = resolve_org(sdk)\n- _, apps, per_app = fetch_live(sdk, org, args.limit)\n+ apps, per_app = fetch_live(sdk, org, args.limit)\n render(org, apps, per_app, args.json)\n \n \n if __name__ == \"__main__\":\n",
"firstChangedLine": 147
}
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-ab643bf9e3c3c146",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Now verify: imports work, mock run works, and that the live path constructs correctly (can't auth). Also verify that an unauthenticated call fails gracefully — the SDK probably raises. Let's test imports + mock.
Also check quick_filter = "OPEN" is valid for ApplicationTraceFilter — check filter_utils/constants. Let me grep for quick filter values.
cd /sandbox/repo && python -c "
from contrast_security.contrast_sdk import ContrastSdk
from contrast_security.filters.application_filter import ApplicationFilter
from contrast_security.filters.application_trace_filter import ApplicationTraceFilter
print('imports ok')
f=ApplicationTraceFilter(); print(list(f.get_query_params_as_json().items())[:6])
" && python contrast_inventory.py --mock && python contrast_inventory.py --mock --json | head -12 && python contrast_inventory.py 2>&1 | tail -2imports ok
imports ok
[('expand', ''), ('limit', 20), ('offset', 0), ('sort', '-lastTimeSeen')]
Organization: mock-org-uuid
Applications: 2
billing-service (app-billing, JAVA, importance=CRITICAL) -> 2 open findings
[CRITICAL] SQL Injection route=/api/invoices status=REPORTED uuid=t-1
[HIGH ] Path Traversal route=/api/export status=REPORTED uuid=t-2
storefront (app-store, JAVASCRIPT, importance=HIGH) -> 1 open findings
[MEDIUM ] Cross-Site Scripting (Reflected) route=/search status=REMEDIATED uuid=t-3
Open findings by severity:
CRITICAL 1
HIGH 1
MEDIUM 1
{
"org": "mock-org-uuid",
"applications": [
{
"app_id": "app-billing",
"name": "billing-service",
"language": "JAVA",
"importance": "CRITICAL",
"last_sha1": "abc123"
},
{
"app_id": "app-store",
CONTRAST_USERNAME is not set. Export it, or run with --mock for an offline demo.
Recorded tool call
{
"type": "tool_call",
"seq": 78,
"ts": "2026-10-06T15:01:02.264Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && python -c \"\nfrom contrast_security.contrast_sdk import ContrastSdk\nfrom contrast_security.filters.application_filter import ApplicationFilter\nfrom contrast_security.filters.application_trace_filter import ApplicationTraceFilter\nprint('imports ok')\nf=ApplicationTraceFilter(); print(list(f.get_query_params_as_json().items())[:6])\n\" && python contrast_inventory.py --mock && python contrast_inventory.py --mock --json | head -12 && python contrast_inventory.py 2>&1 | tail -2"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a08afce072181274",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 81,
"ts": "2026-10-06T15:01:03.038Z",
"turn": 1,
"output": {
"content": [
{
"text": "imports ok\n[('expand', ''), ('limit', 20), ('offset', 0), ('sort', '-lastTimeSeen')]\nOrganization: mock-org-uuid\nApplications: 2\n\n billing-service (app-billing, JAVA, importance=CRITICAL) -> 2 open findings\n [CRITICAL] SQL Injection route=/api/invoices status=REPORTED uuid=t-1\n [HIGH ] Path Traversal route=/api/export status=REPORTED uuid=t-2\n storefront (app-store, JAVASCRIPT, importance=HIGH) -> 1 open findings\n [MEDIUM ] Cross-Site Scripting (Reflected) route=/search status=REMEDIATED uuid=t-3\n\nOpen findings by severity:\n CRITICAL 1\n HIGH 1\n MEDIUM 1\n{\n \"org\": \"mock-org-uuid\",\n \"applications\": [\n {\n \"app_id\": \"app-billing\",\n \"name\": \"billing-service\",\n \"language\": \"JAVA\",\n \"importance\": \"CRITICAL\",\n \"last_sha1\": \"abc123\"\n },\n {\n \"app_id\": \"app-store\",\nCONTRAST_USERNAME is not set. Export it, or run with --mock for an offline demo.\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a08afce072181274",
"is_error": false,
"tool_kind": "shell"
}cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -rn "quick_filter\|quickFilter" filters/filter_utils.py user_api/base_api.py 2>/dev/null | head; grep -rn "OPEN\|ALL" filters/application_trace_filter.py | head
32: self.quick_filter = "ALL"
32: self.quick_filter = "ALL"
Recorded tool call
{
"type": "tool_call",
"seq": 79,
"ts": "2026-10-06T15:01:02.264Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && grep -rn \"quick_filter\\|quickFilter\" filters/filter_utils.py user_api/base_api.py 2>/dev/null | head; grep -rn \"OPEN\\|ALL\" filters/application_trace_filter.py | head"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b73dcedf0772533c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 80,
"ts": "2026-10-06T15:01:02.264Z",
"turn": 1,
"output": {
"content": [
{
"text": "32: self.quick_filter = \"ALL\"\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b73dcedf0772533c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Works. quick_filter = "OPEN" — valid per Contrast REST API (quickFilter=OPEN). Fine.
Also verify the live path fails gracefully with bad creds rather than hanging. Test with dummy creds and short timeout — the request will 401. SDK likely raises HTTPError or returns. Let's test quickly with a timeout guard (short, no long-running).
cd /sandbox/repo && CONTRAST_USERNAME=x@example.com CONTRAST_API_KEY=k CONTRAST_SERVICE_KEY=c timeout 25 python contrast_inventory.py 2>&1 | tail -5; echo "exit=$?"
No organization visible to this user; set CONTRAST_ORG_UUID explicitly.
No organization visible to this user; set CONTRAST_ORG_UUID explicitly. exit=0
Recorded tool call
{
"type": "tool_call",
"seq": 84,
"ts": "2026-10-06T15:01:05.618Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && CONTRAST_USERNAME=x@example.com CONTRAST_API_KEY=k CONTRAST_SERVICE_KEY=c timeout 25 python contrast_inventory.py 2>&1 | tail -5; echo \"exit=$?\""
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-bc63cfcf8766bcfe",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 85,
"ts": "2026-10-06T15:01:06.897Z",
"turn": 1,
"output": {
"content": [
{
"text": "No organization visible to this user; set CONTRAST_ORG_UUID explicitly.\nexit=0\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-bc63cfcf8766bcfe",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Hmm, with bad creds the SDK returned empty rather than raising — meaning errors are silently swallowed. Better: detect auth failure. Let's check how base api handles responses (probably returns None on non-200). I should surface a clear message. Let me check the base api.
cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && ls; grep -rn "def _get\|status_code\|raise_for_status" --include=*.py . | grep -v pycache | head -15
__init__.py
__init__.py __pycache__ contrast_sdk.py filters types user_api ./user_api/api_support.py:14: def _get(self, path, params=None):
Recorded tool call
{
"type": "tool_call",
"seq": 88,
"ts": "2026-10-06T15:01:09.251Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /opt/freestyle/python/lib/python3.12/site-packages/contrast_security && ls; grep -rn \"def _get\\|status_code\\|raise_for_status\" --include=*.py . | grep -v pycache | head -15"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b978cdd1dec2a226",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 89,
"ts": "2026-10-06T15:01:09.251Z",
"turn": 1,
"output": {
"content": [
{
"text": "__init__.py\n__pycache__\ncontrast_sdk.py\nfilters\ntypes\nuser_api\n./user_api/api_support.py:14: def _get(self, path, params=None):\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b978cdd1dec2a226",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Let me look at api_support to see error handling, then make my script verify auth explicitly.
✓read /opt/freestyle/python/lib/python3.12/site-packages/contrast_security/user_api/api_support.py✓└{"content":[{"text":"import requests\n\n\nclass _ApiSupport(object):\n _headers = None\n _base_url = None\n\n def __init__(self, version='ng'):\n …ctrl+o
{"content":[{"text":"import requests\n\n\nclass _ApiSupport(object):\n _headers = None\n _base_url = None\n\n def __init__(self, version='ng'):\n self._version = version\n\n def build_url(self, path):\n return '{base_url}/{version}/{path}'.format(base_url=self._base_url, version=self._version, path=path)\n\n def _get(self, path, params=None):\n return requests.get(self.build_url(path), params=params, headers=self._headers)\n\n def _post(self, path, data={}, json=None, params=None):\n return requests.post(self.build_url(path), data=data, json=json, headers=self._headers, params=params)\n\n def _put(self, path, data=None):\n return requests.put(self.build_url(path), json=data, headers=self._headers)\n\n def _download(self, path, params=None):\n return requests.get(self.build_url(path), params=params, headers=self._headers, stream=True)\n\n def _delete(self, path, data=None):\n return requests.delete(self.build_url(path), json=data, headers=self._headers)\n\n","type":"text"}]}›Thinking…
The SDK returns raw requests.Response objects — not parsed JSON! So .get(...) on the response won't work. That's an important correction: my script assumes dicts. I need to unwrap responses: resp.raise_for_status(); resp.json().
Let me confirm by checking the SDK docs/README or a types module. Check github README? The SDK returns Response objects; users call .json(). Let me verify quickly with a live unauthenticated call.
cd /sandbox/repo && python - <<'EOF'
from contrast_security.contrast_sdk import ContrastSdk
sdk = ContrastSdk("x@example.com","k","c")
r = sdk.get_profile_default_organization()
print(type(r), r.status_code, r.text[:200])
EOF<class 'requests.models.Response'> 401 {"timestamp":"2026-10-06T15:01:14.676Z","path":"/Contrast/api/ng/profile/organizations/default","status":401,"error":"Una
<class 'requests.models.Response'> 401 {"timestamp":"2026-10-06T15:01:14.676Z","path":"/Contrast/api/ng/profile/organizations/default","status":401,"error":"Unauthorized","requestId":"968510dc-355119"}
Recorded tool call
{
"type": "tool_call",
"seq": 96,
"ts": "2026-10-06T15:01:14.514Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && python - <<'EOF'\nfrom contrast_security.contrast_sdk import ContrastSdk\nsdk = ContrastSdk(\"x@example.com\",\"k\",\"c\")\nr = sdk.get_profile_default_organization()\nprint(type(r), r.status_code, r.text[:200])\nEOF"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a63fe3ca6592dc05",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 97,
"ts": "2026-10-06T15:01:15.400Z",
"turn": 1,
"output": {
"content": [
{
"text": "<class 'requests.models.Response'> 401 {\"timestamp\":\"2026-10-06T15:01:14.676Z\",\"path\":\"/Contrast/api/ng/profile/organizations/default\",\"status\":401,\"error\":\"Unauthorized\",\"requestId\":\"968510dc-355119\"}\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-a63fe3ca6592dc05",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Confirmed: raw Response objects, and auth reaches the live API (401 with dummy creds → endpoint live and reachable). Now fix the script to unwrap responses with clear error handling.
Update: add helper unwrap(resp) that raises SystemExit with status + body snippet on non-2xx, else returns resp.json(). Apply in resolve_org and fetch_live.
File edits
1 file
Modifiedcontrast_inventory.py+15−6View changes
/sandbox/repo/contrast_inventory.py
Replacement excerpt
+def unwrap(response, action: str):+ """The SDK returns raw requests.Response objects; turn them into JSON or fail loudly."""+ if response.status_code == 401:+ sys.exit(f"{action}: 401 Unauthorized. Check CONTRAST_USERNAME / CONTRAST_API_KEY / "+ "CONTRAST_SERVICE_KEY (and CONTRAST_URL if you are not on the SaaS TeamServer).")+ if response.status_code >= 300:+ sys.exit(f"{action}: HTTP {response.status_code} {response.text[:300]}")+ return response.json()++def resolve_org(sdk) -> str:org = env("CONTRAST_ORG_UUID", required=False)if org:return org- default = sdk.get_profile_default_organization()+ default = unwrap(sdk.get_profile_default_organization(), "resolve default organization")org = (default or {}).get("organization_uuid")if not org:- orgs = (sdk.get_profile_organizations() or {}).get("organizations", [])+ orgs = unwrap(sdk.get_profile_organizations(), "list organizations").get("organizations", [])org = orgs[0]["organization_uuid"] if orgs else None
Replacement excerpt
- apps = (sdk.filter_applications(org, app_filter) or {}).get("applications", [])+ apps = unwrap(sdk.filter_applications(org, app_filter), "list applications").get("applications", [])per_app = {}for app in apps:trace_filter = ApplicationTraceFilter()trace_filter.app_id = app["app_id"]trace_filter.limit = limittrace_filter.quick_filter = "OPEN"- per_app[app["app_id"]] = (sdk.filter_application_traces(org, app["app_id"], trace_filter) or {}).get(- "traces", []- )+ resp = sdk.filter_application_traces(org, app["app_id"], trace_filter)+ per_app[app["app_id"]] = unwrap(resp, f"list traces for {app['app_id']}").get("traces", [])
Source: edit · chatcmpl-tool-abbaeeb589ae407e
Recorded tool call
{
"type": "tool_call",
"seq": 100,
"ts": "2026-10-06T15:01:23.245Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/contrast_inventory.py",
"edits": [
{
"newText": "def unwrap(response, action: str):\n \"\"\"The SDK returns raw requests.Response objects; turn them into JSON or fail loudly.\"\"\"\n if response.status_code == 401:\n sys.exit(f\"{action}: 401 Unauthorized. Check CONTRAST_USERNAME / CONTRAST_API_KEY / \"\n \"CONTRAST_SERVICE_KEY (and CONTRAST_URL if you are not on the SaaS TeamServer).\")\n if response.status_code >= 300:\n sys.exit(f\"{action}: HTTP {response.status_code} {response.text[:300]}\")\n return response.json()\n\n\ndef resolve_org(sdk) -> str:\n org = env(\"CONTRAST_ORG_UUID\", required=False)\n if org:\n return org\n default = unwrap(sdk.get_profile_default_organization(), \"resolve default organization\")\n org = (default or {}).get(\"organization_uuid\")\n if not org:\n orgs = unwrap(sdk.get_profile_organizations(), \"list organizations\").get(\"organizations\", [])\n org = orgs[0][\"organization_uuid\"] if orgs else None",
"oldText": "def resolve_org(sdk) -> str:\n org = env(\"CONTRAST_ORG_UUID\", required=False)\n if org:\n return org\n default = sdk.get_profile_default_organization()\n org = (default or {}).get(\"organization_uuid\")\n if not org:\n orgs = (sdk.get_profile_organizations() or {}).get(\"organizations\", [])\n org = orgs[0][\"organization_uuid\"] if orgs else None"
},
{
"newText": " apps = unwrap(sdk.filter_applications(org, app_filter), \"list applications\").get(\"applications\", [])\n\n per_app = {}\n for app in apps:\n trace_filter = ApplicationTraceFilter()\n trace_filter.app_id = app[\"app_id\"]\n trace_filter.limit = limit\n trace_filter.quick_filter = \"OPEN\"\n resp = sdk.filter_application_traces(org, app[\"app_id\"], trace_filter)\n per_app[app[\"app_id\"]] = unwrap(resp, f\"list traces for {app['app_id']}\").get(\"traces\", [])",
"oldText": " apps = (sdk.filter_applications(org, app_filter) or {}).get(\"applications\", [])\n\n per_app = {}\n for app in apps:\n trace_filter = ApplicationTraceFilter()\n trace_filter.app_id = app[\"app_id\"]\n trace_filter.limit = limit\n trace_filter.quick_filter = \"OPEN\"\n per_app[app[\"app_id\"]] = (sdk.filter_application_traces(org, app[\"app_id\"], trace_filter) or {}).get(\n \"traces\", []\n )"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-abbaeeb589ae407e",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 101,
"ts": "2026-10-06T15:01:23.245Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 2 block(s) in /sandbox/repo/contrast_inventory.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 71 teamserver_url=os.environ.get(\"CONTRAST_URL\", \"https://app.contrastsecurity.com\"),\n 72 )\n 73 \n 74 \n+ 75 def unwrap(response, action: str):\n+ 76 \"\"\"The SDK returns raw requests.Response objects; turn them into JSON or fail loudly.\"\"\"\n+ 77 if response.status_code == 401:\n+ 78 sys.exit(f\"{action}: 401 Unauthorized. Check CONTRAST_USERNAME / CONTRAST_API_KEY / \"\n+ 79 \"CONTRAST_SERVICE_KEY (and CONTRAST_URL if you are not on the SaaS TeamServer).\")\n+ 80 if response.status_code >= 300:\n+ 81 sys.exit(f\"{action}: HTTP {response.status_code} {response.text[:300]}\")\n+ 82 return response.json()\n+ 83 \n+ 84 \n 75 def resolve_org(sdk) -> str:\n 76 org = env(\"CONTRAST_ORG_UUID\", required=False)\n 77 if org:\n 78 return org\n- 79 default = sdk.get_profile_default_organization()\n+ 89 default = unwrap(sdk.get_profile_default_organization(), \"resolve default organization\")\n 80 org = (default or {}).get(\"organization_uuid\")\n 81 if not org:\n- 82 orgs = (sdk.get_profile_organizations() or {}).get(\"organizations\", [])\n+ 92 orgs = unwrap(sdk.get_profile_organizations(), \"list organizations\").get(\"organizations\", [])\n 83 org = orgs[0][\"organization_uuid\"] if orgs else None\n 84 if not org:\n 85 sys.exit(\"No organization visible to this user; set CONTRAST_ORG_UUID explicitly.\")\n 86 return org\n ...\n 89 def fetch_live(sdk, org: str, limit: int):\n 90 app_filter = ApplicationFilter()\n 91 app_filter.limit = limit\n 92 app_filter.expand = [ApplicationFilter.ExpandTraceBreakdown]\n- 93 apps = (sdk.filter_applications(org, app_filter) or {}).get(\"applications\", [])\n+103 apps = unwrap(sdk.filter_applications(org, app_filter), \"list applications\").get(\"applications\", [])\n 94 \n 95 per_app = {}\n 96 for app in apps:\n 97 trace_filter = ApplicationTraceFilter()\n 98 trace_filter.app_id = app[\"app_id\"]\n 99 trace_filter.limit = limit\n 100 trace_filter.quick_filter = \"OPEN\"\n-101 per_app[app[\"app_id\"]] = (sdk.filter_application_traces(org, app[\"app_id\"], trace_filter) or {}).get(\n-102 \"traces\", []\n-103 )\n+111 resp = sdk.filter_application_traces(org, app[\"app_id\"], trace_filter)\n+112 per_app[app[\"app_id\"]] = unwrap(resp, f\"list traces for {app['app_id']}\").get(\"traces\", [])\n 104 return apps, per_app\n 105 \n 106 \n 107 def render(org, apps, per_app, raw: bool) -> None:\n ...",
"patch": "--- /sandbox/repo/contrast_inventory.py\n+++ /sandbox/repo/contrast_inventory.py\n@@ -71,16 +71,26 @@\n teamserver_url=os.environ.get(\"CONTRAST_URL\", \"https://app.contrastsecurity.com\"),\n )\n \n \n+def unwrap(response, action: str):\n+ \"\"\"The SDK returns raw requests.Response objects; turn them into JSON or fail loudly.\"\"\"\n+ if response.status_code == 401:\n+ sys.exit(f\"{action}: 401 Unauthorized. Check CONTRAST_USERNAME / CONTRAST_API_KEY / \"\n+ \"CONTRAST_SERVICE_KEY (and CONTRAST_URL if you are not on the SaaS TeamServer).\")\n+ if response.status_code >= 300:\n+ sys.exit(f\"{action}: HTTP {response.status_code} {response.text[:300]}\")\n+ return response.json()\n+\n+\n def resolve_org(sdk) -> str:\n org = env(\"CONTRAST_ORG_UUID\", required=False)\n if org:\n return org\n- default = sdk.get_profile_default_organization()\n+ default = unwrap(sdk.get_profile_default_organization(), \"resolve default organization\")\n org = (default or {}).get(\"organization_uuid\")\n if not org:\n- orgs = (sdk.get_profile_organizations() or {}).get(\"organizations\", [])\n+ orgs = unwrap(sdk.get_profile_organizations(), \"list organizations\").get(\"organizations\", [])\n org = orgs[0][\"organization_uuid\"] if orgs else None\n if not org:\n sys.exit(\"No organization visible to this user; set CONTRAST_ORG_UUID explicitly.\")\n return org\n@@ -89,19 +99,18 @@\n def fetch_live(sdk, org: str, limit: int):\n app_filter = ApplicationFilter()\n app_filter.limit = limit\n app_filter.expand = [ApplicationFilter.ExpandTraceBreakdown]\n- apps = (sdk.filter_applications(org, app_filter) or {}).get(\"applications\", [])\n+ apps = unwrap(sdk.filter_applications(org, app_filter), \"list applications\").get(\"applications\", [])\n \n per_app = {}\n for app in apps:\n trace_filter = ApplicationTraceFilter()\n trace_filter.app_id = app[\"app_id\"]\n trace_filter.limit = limit\n trace_filter.quick_filter = \"OPEN\"\n- per_app[app[\"app_id\"]] = (sdk.filter_application_traces(org, app[\"app_id\"], trace_filter) or {}).get(\n- \"traces\", []\n- )\n+ resp = sdk.filter_application_traces(org, app[\"app_id\"], trace_filter)\n+ per_app[app[\"app_id\"]] = unwrap(resp, f\"list traces for {app['app_id']}\").get(\"traces\", [])\n return apps, per_app\n \n \n def render(org, apps, per_app, raw: bool) -> None:\n",
"firstChangedLine": 75
}
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-abbaeeb589ae407e",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Now re-test: mock, and bad-creds live path should give clear 401 message. Then write README with pricing + credentials info.
cd /sandbox/repo && python contrast_inventory.py --mock | tail -6 && echo "--- live w/ dummy creds ---" && CONTRAST_USERNAME=x@example.com CONTRAST_API_KEY=k CONTRAST_SERVICE_KEY=c timeout 25 python contrast_inventory.py; echo "exit=$?"; python -m py_compile contrast_inventory.py && echo compile-ok
[MEDIUM ] Cross-Site Scripting (Reflected) route=/search status=REMEDIATED uuid=t-3
[MEDIUM ] Cross-Site Scripting (Reflected) route=/search status=REMEDIATED uuid=t-3 Open findings by severity: CRITICAL 1 HIGH 1 MEDIUM 1 --- live w/ dummy creds --- resolve default organization: 401 Unauthorized. Check CONTRAST_USERNAME / CONTRAST_API_KEY / CONTRAST_SERVICE_KEY (and CONTRAST_URL if you are not on the SaaS TeamServer). exit=1 compile-ok
Recorded tool call
{
"type": "tool_call",
"seq": 104,
"ts": "2026-10-06T15:01:25.487Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && python contrast_inventory.py --mock | tail -6 && echo \"--- live w/ dummy creds ---\" && CONTRAST_USERNAME=x@example.com CONTRAST_API_KEY=k CONTRAST_SERVICE_KEY=c timeout 25 python contrast_inventory.py; echo \"exit=$?\"; python -m py_compile contrast_inventory.py && echo compile-ok"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-8dc94a17d777b206",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 105,
"ts": "2026-10-06T15:01:26.258Z",
"turn": 1,
"output": {
"content": [
{
"text": " [MEDIUM ] Cross-Site Scripting (Reflected) route=/search status=REMEDIATED uuid=t-3\n\nOpen findings by severity:\n CRITICAL 1\n HIGH 1\n MEDIUM 1\n--- live w/ dummy creds ---\nresolve default organization: 401 Unauthorized. Check CONTRAST_USERNAME / CONTRAST_API_KEY / CONTRAST_SERVICE_KEY (and CONTRAST_URL if you are not on the SaaS TeamServer).\nexit=1\ncompile-ok\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-8dc94a17d777b206",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Now the README with pricing (grounded in fetched pricing page), credentials setup, and a note on manageability. Also add .env.example. Then git commit? The repo has a git history; committing is nice but optional. I'll commit.
Write README.
File edits
1 file
WrittenREADME.mdView changes
/sandbox/repo/README.md
Written content · previous contents were not recorded
# Contrast Security — minimal API example`contrast_inventory.py` walks the hosted Contrast TeamServer API through the official PythonSDK (`contrast-security` on PyPI): **profile → organization → applications → open vulnerabilities("traces")**, then prints a severity rollup. It talks to the SaaS endpoint`https://app.contrastsecurity.com/Contrast/api/ng/...`. No local services, no agents to run.## Run it offline (no account needed)```bashpip install -r requirements.txtpython contrast_inventory.py --mock # fixture data, exercises the whole render pathpython contrast_inventory.py --mock --json # same data as raw JSON```## Run it against your org1. In the Contrast UI: **Your Account → API Keys** → copy the **API key** and **Service key**.2. Export credentials (never commit them):```bashexport CONTRAST_USERNAME="you@example.com"export CONTRAST_API_KEY="..."export CONTRAST_SERVICE_KEY="..."export CONTRAST_ORG_UUID="..." # optional; auto-discovered from your profile otherwiseexport CONTRAST_URL="https://app.contrastsecurity.com" # or your private TeamServer```3. ```bashpython contrast_inventory.py # summarypython contrast_inventory.py --json # raw API JSONpython contrast_inventory.py --limit 50 # page size per call```Auth failures exit non-zero with a clear message instead of printing empty results.## SDK notes that are easy to get wrong- The package ships **empty `__init__.py` files**, so import fully qualified:`from contrast_security.contrast_sdk import ContrastSdk` and`from contrast_security.filters.application_filter import ApplicationFilter`.- The class is `ContrastSdk` (not `ContrastSecuritySDK`).- Every call returns a **raw `requests.Response`**, not parsed JSON — call `.json()` yourselfand check the status code (the `unwrap()` helper here does both).- Auth is two headers: `API-Key: <api_key>` plus `Authorization: Base64(user:service_key)`,which `ContrastSdk` builds for you.## Pricing (as published on contrastsecurity.com/pricing)Pricing is per **application**, metered in **services** (independently deployable runtimecomponents; a few services make up one application), and billed annually:| Tier | Price | Coverage | Notes || --- | --- | --- | --- || **Free** (CVE Shield) | $0, no credit card | 2 applications / up to 12 services | Observation-only (detects CVE exploitation, does not block), runtime SCA + dependency tree, 14-day history, 1 user, community support. Java on Linux at launch. || **Pro** | $750/month billed annually ($9,000/year) — listed as "to be released soon" | 8 applications / up to 50 services | Adds active runtime blocking, 90-day history, up to 5 users, email support. || **Enterprise** | Custom quote from sales | Full platform (ADR, AST, SCA) | Annual consumption pricing based on production hosts; SIEM/ticketing integrations, compliance reporting, RBAC + SAML, 1-year history, unlimited users, SLA-backed support. |Practical read: the published tiers cover **CVE Shield / runtime protection** only. The broaderContrast platform (Assess IAST/SAST, SCA, Protect, private TeamServer hosting) has **no publicprice list** — it is sales-quoted, typically per-application/per-module annual subscriptions withvolume tiers. Budget for a sales conversation if you need anything beyond the free CVE Shield tier,and note that API access in practice comes with a real (Enterprise) account.## Can an AI agent manage this day to day?Mostly yes for the read/ops side, with one hard gate:- **Easy:** the REST API is broad and stable (applications, traces/vulnerabilities, libraries,servers, scores, policies, webhooks, users, roles, route coverage), there are official SDKs forPython/Java/.NET, JSON in/out, header auth, and paginated filter endpoints — all well suited toscripted reporting, triage automation, and CI/CD gating. This example is ~150 lines and coversthe main inventory flow.- **Blocking constraint:** everything requires account credentials (username + API key + servicekey + org UUID) from an org you already own. There is no self-serve signup or public sandbox forthe full platform, so an agent can build and verify integrations offline (`--mock`) but cannotexercise live endpoints until you supply keys. The Python SDK also does thin response handling(raw `Response` objects, no retries/typing), so error handling and pagination must be written byhand.
Source: write · chatcmpl-tool-80351dd12db18f83
Recorded tool call
{
"type": "tool_call",
"seq": 108,
"ts": "2026-10-06T15:01:45.967Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/README.md",
"content": "# Contrast Security — minimal API example\n\n`contrast_inventory.py` walks the hosted Contrast TeamServer API through the official Python\nSDK (`contrast-security` on PyPI): **profile → organization → applications → open vulnerabilities\n(\"traces\")**, then prints a severity rollup. It talks to the SaaS endpoint\n`https://app.contrastsecurity.com/Contrast/api/ng/...`. No local services, no agents to run.\n\n## Run it offline (no account needed)\n\n```bash\npip install -r requirements.txt\npython contrast_inventory.py --mock # fixture data, exercises the whole render path\npython contrast_inventory.py --mock --json # same data as raw JSON\n```\n\n## Run it against your org\n\n1. In the Contrast UI: **Your Account → API Keys** → copy the **API key** and **Service key**.\n2. Export credentials (never commit them):\n\n ```bash\n export CONTRAST_USERNAME=\"you@example.com\"\n export CONTRAST_API_KEY=\"...\"\n export CONTRAST_SERVICE_KEY=\"...\"\n export CONTRAST_ORG_UUID=\"...\" # optional; auto-discovered from your profile otherwise\n export CONTRAST_URL=\"https://app.contrastsecurity.com\" # or your private TeamServer\n ```\n\n3. ```bash\n python contrast_inventory.py # summary\n python contrast_inventory.py --json # raw API JSON\n python contrast_inventory.py --limit 50 # page size per call\n ```\n\nAuth failures exit non-zero with a clear message instead of printing empty results.\n\n## SDK notes that are easy to get wrong\n\n- The package ships **empty `__init__.py` files**, so import fully qualified:\n `from contrast_security.contrast_sdk import ContrastSdk` and\n `from contrast_security.filters.application_filter import ApplicationFilter`.\n- The class is `ContrastSdk` (not `ContrastSecuritySDK`).\n- Every call returns a **raw `requests.Response`**, not parsed JSON — call `.json()` yourself\n and check the status code (the `unwrap()` helper here does both).\n- Auth is two headers: `API-Key: <api_key>` plus `Authorization: Base64(user:service_key)`,\n which `ContrastSdk` builds for you.\n\n## Pricing (as published on contrastsecurity.com/pricing)\n\nPricing is per **application**, metered in **services** (independently deployable runtime\ncomponents; a few services make up one application), and billed annually:\n\n| Tier | Price | Coverage | Notes |\n| --- | --- | --- | --- |\n| **Free** (CVE Shield) | $0, no credit card | 2 applications / up to 12 services | Observation-only (detects CVE exploitation, does not block), runtime SCA + dependency tree, 14-day history, 1 user, community support. Java on Linux at launch. |\n| **Pro** | $750/month billed annually ($9,000/year) — listed as \"to be released soon\" | 8 applications / up to 50 services | Adds active runtime blocking, 90-day history, up to 5 users, email support. |\n| **Enterprise** | Custom quote from sales | Full platform (ADR, AST, SCA) | Annual consumption pricing based on production hosts; SIEM/ticketing integrations, compliance reporting, RBAC + SAML, 1-year history, unlimited users, SLA-backed support. |\n\nPractical read: the published tiers cover **CVE Shield / runtime protection** only. The broader\nContrast platform (Assess IAST/SAST, SCA, Protect, private TeamServer hosting) has **no public\nprice list** — it is sales-quoted, typically per-application/per-module annual subscriptions with\nvolume tiers. Budget for a sales conversation if you need anything beyond the free CVE Shield tier,\nand note that API access in practice comes with a real (Enterprise) account.\n\n## Can an AI agent manage this day to day?\n\nMostly yes for the read/ops side, with one hard gate:\n\n- **Easy:** the REST API is broad and stable (applications, traces/vulnerabilities, libraries,\n servers, scores, policies, webhooks, users, roles, route coverage), there are official SDKs for\n Python/Java/.NET, JSON in/out, header auth, and paginated filter endpoints — all well suited to\n scripted reporting, triage automation, and CI/CD gating. This example is ~150 lines and covers\n the main inventory flow.\n- **Blocking constraint:** everything requires account credentials (username + API key + service\n key + org UUID) from an org you already own. There is no self-serve signup or public sandbox for\n the full platform, so an agent can build and verify integrations offline (`--mock`) but cannot\n exercise live endpoints until you supply keys. The Python SDK also does thin response handling\n (raw `Response` objects, no retries/typing), so error handling and pagination must be written by\n hand.\n"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-80351dd12db18f83",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 111,
"ts": "2026-10-06T15:01:45.967Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 4489 bytes to /sandbox/repo/README.md",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-80351dd12db18f83",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Written.env.exampleView changes
/sandbox/repo/.env.example
Written content · previous contents were not recorded
# Copy to your shell env or a secrets manager. Do not commit real values.CONTRAST_USERNAME=you@example.comCONTRAST_API_KEY=CONTRAST_SERVICE_KEY=# Optional: auto-discovered from your profile when unset.CONTRAST_ORG_UUID=# Optional: defaults to the hosted SaaS TeamServer.CONTRAST_URL=https://app.contrastsecurity.com
Source: write · chatcmpl-tool-95782644dae6987f
Recorded tool call
{
"type": "tool_call",
"seq": 109,
"ts": "2026-10-06T15:01:45.967Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/.env.example",
"content": "# Copy to your shell env or a secrets manager. Do not commit real values.\nCONTRAST_USERNAME=you@example.com\nCONTRAST_API_KEY=\nCONTRAST_SERVICE_KEY=\n# Optional: auto-discovered from your profile when unset.\nCONTRAST_ORG_UUID=\n# Optional: defaults to the hosted SaaS TeamServer.\nCONTRAST_URL=https://app.contrastsecurity.com\n"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-95782644dae6987f",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 110,
"ts": "2026-10-06T15:01:45.967Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 323 bytes to /sandbox/repo/.env.example",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-95782644dae6987f",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Check .gitignore doesn't ignore .env.example oddly, then commit.
cd /sandbox/repo && cat .gitignore | head -20; git add -A && git status --short && git -c user.email=dev@example.com -c user.name=dev commit -q -m "Add Contrast Security API example (SDK-based inventory report)" && git log --oneline -2
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs. # Logs / OS cruft *.log .DS_Store # JavaScript / TypeScript node_modules/ .next/ dist/ build/ coverage/ # Python .venv/ venv/ __pycache__/ *.py[cod] .pytest_cache/ *.egg-info/ A .env.example A README.md A contrast_inventory.py A requirements.txt 2048e83 Add Contrast Security API example (SDK-based inventory report) 9ebf5fb Initial workspace
Recorded tool call
{
"type": "tool_call",
"seq": 114,
"ts": "2026-10-06T15:01:52.202Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && cat .gitignore | head -20; git add -A && git status --short && git -c user.email=dev@example.com -c user.name=dev commit -q -m \"Add Contrast Security API example (SDK-based inventory report)\" && git log --oneline -2"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b6241e51229421ca",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 115,
"ts": "2026-10-06T15:01:52.202Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\nA .env.example\nA README.md\nA contrast_inventory.py\nA requirements.txt\n2048e83 Add Contrast Security API example (SDK-based inventory report)\n9ebf5fb Initial workspace\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-b6241e51229421ca",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Clean up pycache from py_compile? .gitignore covers pycache/, and git status showed nothing extra. Remove it anyway.
Done. Write final answer: what I built, pricing, manageability, blocked status (credentials needed for live calls; no local stacks used).
cd /sandbox/repo && rm -rf __pycache__ && ls -la
total 40
total 40 drwxr-xr-x 3 root root 4096 Oct 6 15:01 . drwxr-xr-x 7 root root 4096 Oct 6 14:59 .. -rw------- 1 root root 323 Oct 6 15:01 .env.example drwxr-xr-x 8 root root 4096 Oct 6 15:01 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore -rw------- 1 root root 4509 Oct 6 15:01 README.md -rw------- 1 root root 6571 Oct 6 15:01 contrast_inventory.py -rw------- 1 root root 24 Oct 6 15:00 requirements.txt
Recorded tool call
{
"type": "tool_call",
"seq": 118,
"ts": "2026-10-06T15:01:55.614Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && rm -rf __pycache__ && ls -la"
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-9cccc27857a6ce95",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 119,
"ts": "2026-10-06T15:01:55.614Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 40\ndrwxr-xr-x 3 root root 4096 Oct 6 15:01 .\ndrwxr-xr-x 7 root root 4096 Oct 6 14:59 ..\n-rw------- 1 root root 323 Oct 6 15:01 .env.example\ndrwxr-xr-x 8 root root 4096 Oct 6 15:01 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n-rw------- 1 root root 4509 Oct 6 15:01 README.md\n-rw------- 1 root root 6571 Oct 6 15:01 contrast_inventory.py\n-rw------- 1 root root 24 Oct 6 15:00 requirements.txt\n",
"type": "text"
}
]
},
"run_id": "cmuwt1f4n019w0ivpgrzgpt2u",
"tool_id": "chatcmpl-tool-9cccc27857a6ce95",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Final answer.
Contrast Security does have a real developer workflow (hosted TeamServer REST API + official Python SDK), so I built the example rather than stopping.
Files (committed as 2048e83)
/sandbox/repo/contrast_inventory.py— profile → org → applications → open vulnerabilities ("traces"), with severity rollup,--json,--limit, and--mockoffline mode/sandbox/repo/requirements.txt—contrast-security>=0.23(official SDK)/sandbox/repo/.env.example,/sandbox/repo/README.md
Verified: python contrast_inventory.py --mock runs clean; dummy credentials hit the live SaaS endpoint and fail loudly (resolve default organization: 401 Unauthorized…, exit 1) instead of printing empty results. No local stacks, nothing long-running.
Pricing (fetched live from contrastsecurity.com/pricing — per application, metered in services, billed annually):
- Free (CVE Shield): $0, no card — 2 apps / ≤12 services, observation-only (detects CVE exploitation, no blocking), runtime SCA, 14-day history, 1 user, Java-on-Linux only
- Pro: $750/mo billed annually ($9,000/yr), marked "to be released soon" — 8 apps / ≤50 services, adds active blocking, 90-day history, ≤5 users
- Enterprise: sales quote — annual consumption pricing based on production hosts; full platform (ADR/AST/SCA), SIEM + ticketing, RBAC/SAML, 1-year history, unlimited users, SLAs
The published tiers cover runtime protection only; the broader Contrast platform (Assess IAST/SAST, SCA, Protect, private TeamServer) has no public price list — sales-quoted per-application/per-module subscriptions.
Will it be easy for me to manage? Read/ops side: yes — broad stable REST API (apps, traces, libraries, scores, policies, webhooks, users, route coverage), official SDKs, JSON + header auth, paginated filters; good fit for scripted reporting, triage automation, CI gating. Two frictions: everything is gated on credentials from an org you already own (no self-serve signup or public sandbox for the full platform), and the Python SDK is thin — it returns raw requests.Response objects with no retries/typing, so status checks, .json(), pagination, and error handling are hand-written (the unwrap() helper covers the basics).
Blocked on one thing: live calls need your CONTRAST_USERNAME, CONTRAST_API_KEY, CONTRAST_SERVICE_KEY (UI: Your Account → API Keys) and optionally CONTRAST_ORG_UUID. Give me those and I'll run the real inventory and tune the output against your actual API response shapes.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents