Skip to content

Backend phase 1#1

Merged
selenaschz merged 61 commits into
mainfrom
backend-phase-1
Jul 8, 2026
Merged

Backend phase 1#1
selenaschz merged 61 commits into
mainfrom
backend-phase-1

Conversation

@jcobosmeta

Copy link
Copy Markdown
Collaborator

No description provided.

jcobosmeta and others added 10 commits July 8, 2026 11:23
Add build config, container, and package skeleton for the AgentHound backend
following the spec module layout: pyproject.toml (fastapi, pydantic v2,
networkx, pyyaml), Dockerfile (python:3.11-slim), and ignore rules for the
venv and JSON data directory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the static catalogs the pipeline reads: capabilities.yaml (neutral verbs,
spec section 3), node_properties.yaml (per-type safe defaults, section 4.2),
and rules.yaml (declarative FH-001/002/003/006 descriptors with severity,
recommended controls, and OWASP/MITRE mappings). core/catalogs.py loads and
caches them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pydantic v2 internal representation (spec section 4): a single nodes list with
seven node types plus neutral-capability edges, the Finding model (section
7.1), and the error envelope (section 13). normalizer.py fills safe defaults
and maps concrete tool names to capabilities (sections 4.2 and 6);
validators.py enforces unique ids, resolvable edges, catalog capabilities, and
mandatory finding fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
yaml_loader.py loads YAML safely, rejects empty documents, and detects the
source framework (spec section 5.2). The generic adapter builds the IR from a
thin declarative format; CrewAI, Dify, and LangGraph adapters are Fase 3 stubs
that raise a clear not-implemented error via the dispatcher.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
builder.py turns the IR into a networkx.MultiDiGraph. traversal.py provides the
reachability and path queries from spec section 9 (untrusted inputs, sensitive
data, external outputs), bounded by max_depth=8. serializer.py emits the
frontend graph shape (section 12.2) with node risk scores/badges and per-edge
risk levels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scoring.py implements the additive risk formula, [0,10] clamp, and severity
bands (spec section 10). path_rules.py and node_rules.py implement the Fase 1
rules FH-001/002/003/006; registry.py assembles findings from catalog metadata;
engine.py runs the rule set, sorts by score descending, and assigns stable
finding ids.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
repository.py keeps analyses in memory and mirrors each to JSON on disk (no DB
in Fase 1, spec section 2). logging_util.py emits structured events without
prompts or sensitive data (section 14). pipeline.py wires the stages
end-to-end: YAML to IR to graph to rules to findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FastAPI routes for POST /analyses and the GET graph/findings endpoints (spec
section 12), with errors rendered as the section 13 envelope. main.py assembles
the app, registers routers and exception handlers, and exposes /health and the
auto-generated OpenAPI docs. controls/ is a placeholder for Fase 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixtures for the HireSmart exfiltration case (FH-001) and the fetchWeb indirect
injection case (FH-006), plus pytest suites covering scoring, the generic
adapter, IR validators, graph traversal, the rules engine, and the API
end-to-end. Verifies the Fase 1 acceptance criteria (spec section 16): 26 tests
pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add app/docs/ (README, api, ir_and_rules, glossary, decisions) covering how to
run/build, the endpoint contract, the IR/rules/scoring model, plain-language
terms, and recorded decisions plus deferred Fase 2/3 scope. Update the
top-level README to reflect that Fase 1 is implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the AgentHound Backend “Fase 1 / MVP” as a new FastAPI service that ingests YAML architectures, normalizes into an IR + NetworkX graph, runs initial FH rules, and exposes read endpoints with a spec-style error envelope.

Changes:

  • Add core pipeline: YAML loading + adapter dispatch (generic), IR normalization/validation, graph build + traversal, rule engine (FH-001/002/003/006) with scoring and finding registry.
  • Add FastAPI app with /health, POST /analyses, and graph/findings read endpoints; persist analysis results in-memory and mirrored to disk as JSON.
  • Add developer enablement: docs (API/IR/rules), Dockerfile, pyproject, and a pytest suite with fixtures.

Reviewed changes

Copilot reviewed 50 out of 59 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
README.md Updates repo status/layout to reflect Backend Fase 1 being implemented and points to backend docs.
Backend/pyproject.toml Defines backend package metadata, dependencies, and pytest config.
Backend/Dockerfile Adds a container build for running the FastAPI service.
Backend/.gitignore Ignores venv/data/cache artifacts for the backend.
Backend/.dockerignore Excludes prototype/tests and most markdown from Docker build context (keeps app/docs/README).
Backend/app/init.py Introduces backend package version.
Backend/app/main.py Creates FastAPI app, routers, exception handlers, and CORS middleware.
Backend/app/logging_util.py Adds structured JSON event logging utilities.
Backend/app/docs/README.md Adds run/test instructions and module map for the backend MVP.
Backend/app/docs/api.md Documents the Fase 1 HTTP API contract and error envelope.
Backend/app/docs/ir_and_rules.md Documents IR schema, catalogs, rules, and scoring model.
Backend/app/docs/glossary.md Adds glossary for key backend concepts.
Backend/app/docs/decisions.md Records architectural decisions and deferred scope for later phases.
Backend/app/api/errors.py Implements spec-style error envelope and exception handlers.
Backend/app/api/routes/analyze.py Implements POST /analyses endpoint.
Backend/app/api/routes/graphs.py Implements GET /analyses/{id}/graph endpoint.
Backend/app/api/routes/findings.py Implements GET /analyses/{id}/findings and GET /analyses/{id}/findings/{finding_id}.
Backend/app/core/catalogs.py Adds cached YAML catalog loaders (capabilities, defaults, rules).
Backend/app/core/pipeline.py Implements end-to-end analysis orchestration and persistence.
Backend/app/core/parser/yaml_loader.py Adds YAML parsing + framework detection heuristic.
Backend/app/core/parser/adapters/init.py Adds adapter dispatch and wraps unimplemented adapters into IRValidationError.
Backend/app/core/parser/adapters/generic_adapter.py Implements generic YAML → IR adapter.
Backend/app/core/parser/adapters/crewai_adapter.py Adds Fase 3 stub adapter.
Backend/app/core/parser/adapters/dify_adapter.py Adds Fase 3 stub adapter.
Backend/app/core/parser/adapters/langgraph_adapter.py Adds Fase 3 stub adapter.
Backend/app/core/ir/models.py Adds Pydantic v2 models for IR, findings, summaries, and error envelope.
Backend/app/core/ir/normalizer.py Applies safe defaults and defines tool→capability mapping helpers.
Backend/app/core/ir/validators.py Adds IR + finding validation routines raising IRValidationError.
Backend/app/core/graph/builder.py Builds a NetworkX MultiDiGraph from the IR.
Backend/app/core/graph/traversal.py Adds graph traversal/selectors and path utilities for rules.
Backend/app/core/graph/serializer.py Serializes IR+findings into the frontend graph shape.
Backend/app/core/rules/scoring.py Implements additive risk scoring and severity banding.
Backend/app/core/rules/registry.py Loads rule metadata and assembles Finding objects.
Backend/app/core/rules/path_rules.py Implements FH path-based rules (FH-001/003/006).
Backend/app/core/rules/node_rules.py Implements FH node/reachability rule (FH-002).
Backend/app/core/rules/engine.py Orchestrates rule execution, sorts findings, assigns stable ids.
Backend/app/core/storage/repository.py Adds in-memory + JSON-on-disk persistence repository.
Backend/app/core/controls/.gitkeep Placeholder for future Fase 2 controls modules.
Backend/app/catalogs/capabilities.yaml Adds capability verb catalog used for validation and rules.
Backend/app/catalogs/node_properties.yaml Adds safe defaults and enumerations for node properties.
Backend/app/catalogs/rules.yaml Adds Fase 1 rule descriptors (metadata/control mappings).
Backend/tests/conftest.py Sets test data dir and provides fixture-loading helpers.
Backend/tests/test_api.py Adds API-level tests for health/analyze/graph/findings and error envelopes.
Backend/tests/test_generic_adapter.py Adds tests for generic adapter + normalizer defaults and error handling.
Backend/tests/test_graph_traversal.py Adds tests for traversal selectors/paths/capabilities/reachability.
Backend/tests/test_ir_validators.py Adds tests for IR validation errors (empty, dup ids, dangling edges, unknown cap).
Backend/tests/test_rules_engine.py Adds tests for rule triggering, sorting, and “safe” architecture behavior.
Backend/tests/test_scoring.py Adds scoring and severity-band tests.
Backend/tests/fixtures/hiresmart_generic.yaml Adds representative exfiltration fixture.
Backend/tests/fixtures/hiresmart_fetchweb.yaml Adds indirect-injection fixture for FH-006.
Backend/tests/init.py Marks tests as a package.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Backend/app/core/rules/path_rules.py Outdated
Comment thread Backend/app/core/rules/path_rules.py Outdated
Comment thread Backend/app/core/graph/traversal.py Outdated
Comment thread Backend/app/core/graph/traversal.py
Comment thread Backend/app/core/rules/scoring.py
Comment thread Backend/app/core/rules/engine.py Outdated
Comment thread Backend/app/main.py Outdated
Comment thread Backend/app/core/ir/normalizer.py
Comment thread Backend/app/core/storage/repository.py
jcobosmeta and others added 7 commits July 8, 2026 11:36
Add "info": 0.0 to the BASE table so a base_severity of "info" scores as 0.0
instead of silently falling back to the medium default (4.0).

Addresses Copilot review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_reachable_nodes now uses nx.descendants (one traversal) instead of an
nx.has_path call per node, avoiding O(V) path checks on larger graphs.
edge_ids_on_path collects every parallel edge for each hop rather than only the
first key, so findings on a MultiDiGraph reference all traversed capability
edges.

Addresses Copilot review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_max_sensitivity now skips unknown/malformed sensitivity values instead of
raising ValueError from list.index on unexpected input. The FH-006 fetchWeb
check drops the dead sensitivity == "untrusted" comparison (sensitivity is
enumerated public..critical) and relies on the external boundary signal.

Addresses Copilot review feedback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
analysis_id arrives from URL path params and was interpolated directly into a
filesystem path, so a value with separators or ".." could escape the data
directory and probe arbitrary .json files. Restrict ids to a safe character set
([A-Za-z0-9_-], which still admits UUIDs); get/exists return not-found for
anything else and _path refuses to build an unsafe path. Add regression tests.

Addresses Copilot review feedback (High).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TOOL_CAPABILITY_MAP emitted createResource, search and extractInformation
(per spec section 6), but these were absent from capabilities.yaml, so any IR
synthesized from the map would fail validation as UNKNOWN_CAPABILITY. Add the
three verbs to the catalog so the mapping stays valid for Fase 3 adapters,
rather than dropping information from the map.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wide-open CORS (allow_origins=["*"]) is convenient locally but risky if the
service is exposed. Default to any localhost/127.0.0.1 origin (any port) via a
regex so a local frontend still works, and allow pinning exact origins for
deployment through AGENTHOUND_CORS_ORIGINS.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sort claimed score desc / severity / rule id "for stability" but
reverse=True also sorted rule_id descending. Negate the numeric keys instead so
rule_id stays ascending as the tie-breaker (FH-001 before FH-002 on a tie),
matching the stated intent.

Addresses Copilot review feedback (Low).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 60 changed files in this pull request and generated 8 comments.

Comment thread Backend/app/core/parser/adapters/generic_adapter.py
Comment thread Backend/app/core/ir/validators.py
Comment thread Backend/app/core/pipeline.py
Comment thread Backend/app/core/ir/normalizer.py Outdated
Comment thread Backend/app/docs/ir_and_rules.md Outdated
Comment thread Backend/app/docs/ir_and_rules.md Outdated
Comment thread Backend/.dockerignore Outdated
Comment thread Backend/app/core/ir/models.py
jcobosmeta and others added 7 commits July 8, 2026 12:02
If a caller passed a mapping for 'nodes'/'edges', enumerate iterated its keys
and produced a confusing "must be a mapping" error per key. Validate that both
fields are lists up front and fail with a clear message. Add a regression test.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous checks were near no-ops: the Finding model already guarantees
severity/score by type, and path is never None. Require a non-empty rule_id and
a non-empty path instead. Per spec section 13 only presence is mandated, so
edges/evidence/recommended_controls are deliberately not required non-empty
(future node-only findings may omit them).

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The RULES_EXECUTED telemetry hardcoded rule_count=4, which would drift if the
registered rule set changed. Export RULE_COUNT from core.rules.engine and log
that instead.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The normalizer docstring and the docs implied edges get tool->capability
mapping during analysis, but Fase 1 requires explicit edge capabilities and
normalize() only fills node defaults. Correct both to describe actual behavior
and note the mapping helper is for Fase 3 adapters.

Addresses Copilot review feedback (Low/Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.dockerignore kept app/docs/README.md while excluding the docs it links to, so
the image would carry a README with broken links. The docs are developer
documentation not needed at runtime (the Dockerfile installs dependencies
explicitly rather than building the package), so exclude all of them.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
edge_ids_on_path returns every parallel edge id per hop, so describe edges as
the ids for the path hops including parallel MultiDiGraph edges, rather than a
single edge per hop.

Addresses Copilot review feedback (Low).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The validation section claimed "mandatory finding fields"; state what is
actually enforced -- a non-empty rule_id and path, with severity/score
guaranteed by the model.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bash block used the Windows activation path (.venv/Scripts/activate), which
fails on Linux/macOS. Use the POSIX path and note the Windows PowerShell
alternative.

Addresses Copilot review feedback (Low).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 52 out of 61 changed files in this pull request and generated 1 comment.

Comment thread Backend/app/core/storage/repository.py Outdated
jcobosmeta and others added 2 commits July 8, 2026 15:23
The get() comment still said the corrupt case was "not a 500", but a later
change mapped CORRUPT_ANALYSIS to HTTP 500 in the API layer. Update the comment
to match the actual behavior.

Addresses Copilot review feedback (Low).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lock in the spec section 8 semantics with false-positive guards: FH-001 must
not fire without a read edge into a sensitive asset or without a send into the
output; FH-003 must not fire when the final hop into the output is not a send.
Document that each rule arrow matches a capability on a specific hop, not
anywhere on the path. This closes the negative-coverage gap that let the earlier
"capability anywhere" bugs pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 52 out of 61 changed files in this pull request and generated 2 comments.

Comment thread Backend/app/api/routes/findings.py
Comment thread Backend/app/core/pipeline.py Outdated
jcobosmeta and others added 2 commits July 8, 2026 15:40
get_finding deep-copied the whole IR only to filter nodes/edges for
serialization. It reassigns the nodes/edges lists and never mutates the shared
Node/Edge objects, so a shallow copy suffices and avoids copying the entire
graph.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DEFAULT_MAX_DEPTH was defined in both pipeline.py and graph/traversal.py. Import
it from traversal as the single source of truth to avoid drift.

Addresses Copilot review feedback (Low).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 52 out of 61 changed files in this pull request and generated 3 comments.

Comment thread Backend/app/core/storage/repository.py
Comment thread Backend/app/logging_util.py Outdated
Comment thread Backend/app/core/storage/repository.py Outdated
jcobosmeta and others added 3 commits July 8, 2026 15:51
get() caught FileNotFoundError under the broad OSError clause, so a file
deleted between the exists() check and the read was reported as CORRUPT_ANALYSIS
(500) instead of missing (404); catch it first and return None. save() now
catches OSError (disk full, permissions), cleans up the temp file, and raises a
structured STORAGE_ERROR so the failure keeps the error-envelope contract. Add
regression tests.

Addresses Copilot review feedback (Medium x2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add STORAGE_ERROR to the server-side error codes so a persistence failure is
rendered as a 500 with the error envelope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
log_event serialized metadata with json.dumps, which raises TypeError on a
non-serializable value (Path, enum, datetime) and, since logging runs inline in
the request pipeline, would turn into a 500. Pass default=str so logging cannot
fail the request. Add a test.

Addresses Copilot review feedback (Medium).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 53 out of 62 changed files in this pull request and generated 2 comments.

Comment thread Backend/app/core/parser/adapters/generic_adapter.py Outdated
Comment thread Backend/app/core/parser/adapters/generic_adapter.py Outdated
edges/controls used "data.get(...) or []", which coerced falsy non-list values
(e.g. {}) to [], bypassing the list-type check and silently treating malformed
input as empty. Default only a missing key to []; a present non-list value is
rejected. Add a test.

Addresses Copilot review feedback (Medium x2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 53 out of 62 changed files in this pull request and generated 5 comments.

Comment on lines +91 to +96
raw_nodes = data.get("nodes")
if not raw_nodes:
raise IRValidationError("EMPTY_IR", "Generic input declares no nodes.")
if not isinstance(raw_nodes, list):
raise IRValidationError("INVALID_INPUT", "'nodes' must be a list.", {"got": type(raw_nodes).__name__})

Comment on lines +46 to +55
score = compute_score(
ScoreInputs(
base_severity="critical",
data_sensitivity=_max_sensitivity(cg, path),
external_reach="external",
controls_absent=["approval_missing"],
reversible=True,
agent_count=len(agents),
)
)
Comment on lines +97 to +105
score = compute_score(
ScoreInputs(
base_severity="high",
data_sensitivity=_max_sensitivity(cg, path),
external_reach="external",
controls_absent=["approval_missing"],
agent_count=len(agents),
)
)
Comment on lines +157 to +165
score = compute_score(
ScoreInputs(
base_severity="high",
data_sensitivity=_max_sensitivity(cg, path),
external_reach="external",
controls_absent=["validation_missing", "approval_missing"],
agent_count=len(agents),
)
)
Comment on lines +26 to +35
agents = agents_on_path(cg, path)
score = compute_score(
ScoreInputs(
base_severity="high",
data_sensitivity=max_sensitivity(cg, path),
external_reach="internal",
controls_absent=["allowlist_missing"],
agent_count=len(agents),
)
)
@selenaschz
selenaschz merged commit 18b027d into main Jul 8, 2026
1 check passed
@jcobosmeta
jcobosmeta deleted the backend-phase-1 branch July 9, 2026 09:30
jcobosmeta added a commit that referenced this pull request Jul 13, 2026
- Unify naming: rename backend ReanalyzeRequest/Node/Edge -> Analyze* to match
  the /analyze route and the frontend Analyze* types, and fix the docstring that
  still referenced the old /reanalyze path (Copilot + review #1).
- analyze route: expand the empty-body comment to state that an all-empty edits
  payload is intentionally treated as "analyse the stored graph" and cannot
  arise from the UI (agents are not removable), so it only guards an accidental
  empty body (review #2).
- adapters.toAnalyzeRequest: correct the comment on the deliberate node/edge
  properties asymmetry (edges always send {} since the UI GraphEdge carries no
  properties; existing edge properties are preserved server-side by id) (#4).
- AgentEditModal: only propagate onSave (which marks the graph dirty and
  triggers a re-analysis) when the draft actually changed, so opening and saving
  an agent without edits is a no-op (review #5).

Not changed: toAnalyzeRequest still falls back to capability "" when both
capability and label are nullish (#3) -- in practice every edge carries a
capability, and an empty one is rejected by validate_ir (UNKNOWN_CAPABILITY,
400), so no malformed edge is ever stored.

124 backend tests pass; frontend tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jcobosmeta added a commit that referenced this pull request Jul 13, 2026
…ssing copy

Copilot review follow-ups:

#3 analyze_graph now keeps an existing edge's source/target from the stored IR
(only capability is editable for a known edge id), matching the documented
invariant. Resubmitting a known edge id with different endpoints can no longer
silently rewire the graph. New test covers it.

#2 Processing step copy reflects the parse stage (rules run later): en/es title,
subtitle and doneTitle no longer claim rule-engine execution, and the reasoning
animation lines drop the risk-path/rule/scoring steps in favour of parsing +
capability extraction.

Not changed: #1 (edges send properties: {}) — existing edges keep stored
properties by id server-side and the UI GraphEdge carries none, so nothing is
lost; documented as a deliberate asymmetry.

125 backend tests pass; frontend tsc clean; i18n JSON valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants