Assess a newly deployed system on submission, identify what needs fixing, and open a single Jira ticket for it — automatically.
This is the intake-and-triage layer over the existing security agents. Someone submits a system that just went live; the agent routes it to the right engine, normalizes the findings, and files a ticket the owning team can act on.
flowchart TD
A[Manual submission<br/>name · type · target · owner] --> B{Route by system_type}
B -->|code_repo| C[sast-agent<br/>static analysis]
B -->|web_app| D[dast-agent<br/>dynamic pentest]
B -->|kubernetes| E[kubernetes-audit-agent<br/>cluster audit]
B -->|saas · extension · mcp · other| F[Control checklist<br/>type-specific]
C --> G[Normalize findings]
D --> G
E --> G
F --> G
G --> H[Build one summary ticket]
H --> I{Jira configured?}
I -->|no| J[Dry-run:<br/>render ticket to disk]
I -->|yes| K[Create Jira issue]
Submit a system, get an assessment and a ticket. Nothing is created in Jira unless you configure it — the default is a dry run that writes the exact ticket it would file.
The design principle for v1 is reuse, not reinvention. The three scanning agents already do the hard part; this agent orchestrates them.
| System type | Engine | What runs |
|---|---|---|
code_repo |
sast | sast-agent over the source path |
web_app |
dast | dast-agent against the URL (auth notes via --notes) |
kubernetes |
k8s | kubernetes-audit-agent against the kube-context |
saas |
checklist | SSO, token scope, data residency, vendor assurance, audit logging |
browser_extension |
checklist | permission scope, remote code, CSP, update channel |
mcp |
checklist | tool least-privilege, injection→tool escalation, auth/TLS, secret exposure |
other |
checklist | authn/authz, network exposure, logging baseline |
The sibling agents run as subprocesses, not imports — each is a separate application with its own virtualenv and credentials. The boundary is a process, and it's injectable, so the orchestration is tested without spawning anything.
A purchased SaaS product has no code to scan and a browser extension needs review of its bundle, not a live pentest. For those, the agent emits a type-specific control checklist: each item is a control to verify, surfaced at low/medium/informational severity with the control as its remediation. Nothing in a checklist result claims a confirmed vulnerability — it flags what a human must check before the system is trusted.
Ticketing is behind a small interface with two implementations:
DryRunJiraClient(default) — renders the exact ticket (summary, priority, full finding body) and writes it tooutputs/<system>/jira-ticket-*.txt. Creates nothing. This is what runs with no configuration, so the pipeline is safe to demo and nothing appears in Jira by surprise.HttpJiraClient— files a real issue via the Jira REST API. Enabled only whenJIRA_BASE_URL,JIRA_EMAIL,JIRA_API_TOKEN, andJIRA_PROJECT_KEYare all set. SetJIRA_LIVE=1to require it — the run fails loudly rather than silently dry-running if Jira is half-configured.
One ticket per assessment. Each assessed system produces one summary ticket; every finding is listed in the body, sorted worst-first, and the ticket priority tracks the highest severity found.
uv sync --locked
# SaaS product — runs a control checklist, renders a dry-run ticket
uv run --no-sync python main.py \
--name "acme-analytics" --type saas --target "acme.com" --owner it-sec
# Web app — orchestrates dast-agent (needs dast-agent configured next door)
uv run --no-sync python main.py \
--name "billing-ui" --type web_app --target "http://localhost:5173" \
--notes "test account: user@example.com / <password>"
# Or submit a JSON file
uv run --no-sync python main.py --submission-file submission.jsonsubmission.json:
{
"name": "billing-ui",
"system_type": "web_app",
"target": "http://localhost:5173",
"owner": "payments",
"environment": "staging",
"notes": "auth notes for the test account"
}To file real tickets, copy .env.example to .env and set the JIRA_*
variables. To orchestrate the scanning agents, check them out next to this repo
(or set SAST_AGENT_DIR / DAST_AGENT_DIR / K8S_AGENT_DIR); each sibling needs
its own .env to run.
Dependencies are managed with uv and uv.lock is
committed. The lockfile pins every transitive dependency to an exact version
and a SHA-256 hash per artifact, so uv sync --locked installs the identical
graph every time and refuses to run if the lockfile and pyproject.toml have
drifted. Hash verification is on by default; CI fails if anything disables it.
This is supply-chain hygiene: a re-resolved dependency graph is an unreviewed
change, and the lockfile turns that into a diff you have to approve.
- Least privilege. This agent files a ticket and shells out to the scanning agents; it holds no cluster or cloud credentials of its own. The sibling agents own their own scopes and secrets.
- No secrets in tickets. The ticket body carries findings and remediation, never credentials. Jira auth uses an API token read from the environment.
- Dry-run by default. Ticket creation is opt-in. The agent will not write to Jira unless you configure it.
- Findings are advisory. Output is automated and must be validated by a human before it drives remediation — the summary and every ticket say so.
uv run --no-sync ruff check .
uv run --no-sync pytest -qCovers severity normalization and result aggregation, report parsing for all three sibling formats, subprocess orchestration (mocked runner), the checklist assessors, routing, ticket construction, dry-run/HTTP client selection, and an end-to-end run through the checklist path.
- v1 orchestrates; it does not deep-scan SaaS/extensions/MCP itself. Those
fall back to a control checklist. A later drop-in could add a static analyzer
for extension bundles or an MCP tool-permission scanner behind the same
Assessorinterface. - Sibling agents must be runnable. Orchestrating sast/dast/k8s requires each to be checked out and configured (API keys, targets). Where one isn't available, that assessment fails with a clear error rather than a silent pass.
- Manual submission only. The trigger is a CLI submission. A CI/deploy webhook
or discovery scan would be a thin layer over the same
run()entry point. - Jira only. Ticketing targets Jira. Another tracker would be a new
JiraClient-shaped implementation behind the same interface.