Skip to content

Commit 27eb288

Browse files
authored
fix: repo-wide security hardening from ZAP, Scorecard, and CodeQL audit (#357)
## Summary Repo-wide security hardening based on audit findings from ZAP DAST, OSSF Scorecard, and CodeQL alerts. - **ZAP fixes**: Add `NotFoundException` → 404 handler (prevents 500 on unmatched routes), add `Cross-Origin-Resource-Policy` and `Cache-Control` response headers, add `HTTPException` catch-all handler preserving correct status codes (405, 429, etc.) - **Scorecard fixes**: Pin sandbox Dockerfile images by SHA-256 digest, add `--ignore-scripts` to wrangler CI install, add Dependabot coverage for `docker/sandbox` - **Exception handler hardening**: Static error messages for 401/403 (prevent `exc.detail` leakage), wire `API_ROUTE_NOT_FOUND` event constant for structured logging, add MRO resolution comment - **Documentation**: New comprehensive `docs/security.md` page, landing page Security section with clickable cards, library reference search exclusion, README Security link, scoped `check-yaml --unsafe` to `mkdocs.yml` only - **CodeQL**: Dismissed 3 false-positive alerts via GitHub API (#10, #22, #23) - **Branch protection**: Updated protect-main ruleset with required status checks (`ci-pass`) and PR reviews Closes #356 ## Test plan - [x] `uv run ruff check src/ tests/` — all checks passed - [x] `uv run mypy src/ tests/` — no issues in 917 files - [x] `uv run pytest tests/ -n auto --cov=ai_company --cov-fail-under=80` — 7465 passed, 94.69% coverage - [x] New tests: `test_unmatched_route_returns_404`, `test_method_not_allowed_maps_to_405`, `test_litestar_permission_denied_maps_to_403`, `test_litestar_not_authorized_maps_to_401`, `test_litestar_validation_exception_maps_to_400`, `test_security_response_headers[Strict-Transport-Security]` - [x] Astro landing page builds successfully - [x] Web dashboard: lint (0 errors), type-check, tests (453 passed) ## Review coverage Pre-reviewed by 7 specialized agents (code-reviewer, python-reviewer, pr-test-analyzer, silent-failure-hunter, logging-audit, security-reviewer, docs-consistency). 10 findings implemented across all severity levels.
1 parent 98dd8ca commit 27eb288

30 files changed

Lines changed: 649 additions & 27 deletions

.github/dependabot.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,17 @@ updates:
6262
- Aureliolo
6363
labels:
6464
- type:chore
65+
66+
- package-ecosystem: docker
67+
directory: /docker/sandbox
68+
schedule:
69+
interval: daily
70+
time: "06:00"
71+
timezone: Etc/UTC
72+
commit-message:
73+
prefix: "chore"
74+
open-pull-requests-limit: 5
75+
reviewers:
76+
- Aureliolo
77+
labels:
78+
- type:chore

.github/workflows/dast.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ jobs:
1919
runs-on: ubuntu-latest
2020
permissions:
2121
contents: read
22-
issues: write # Required by API scan action to create issues for scan findings
22+
# The ZAP action creates/updates a single GitHub issue per repo with scan
23+
# findings (not one per run). Removing issues:write would cause the action
24+
# to fail. The auto-created issue is updated in-place on subsequent runs.
25+
issues: write
2326
steps:
2427
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2528
with:

.github/workflows/pages-preview.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ jobs:
233233
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
234234
PR_NUMBER: ${{ needs.build.outputs.pr_number }}
235235
run: |
236-
npm i --no-save wrangler@3.114.17
236+
# Scorecard: npm install cannot be hash-pinned for CI-only tools;
237+
# --ignore-scripts mitigates post-install script supply-chain risk.
238+
npm i --no-save --ignore-scripts wrangler@3.114.17
237239
npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"
238240
239241
- name: Comment preview URL

.pre-commit-config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ repos:
1414
- id: trailing-whitespace
1515
- id: end-of-file-fixer
1616
- id: check-yaml
17-
exclude: ^src/ai_company/templates/builtins/
17+
exclude: ^(src/ai_company/templates/builtins/|mkdocs\.yml$)
18+
- id: check-yaml
19+
name: check-yaml (mkdocs, unsafe for !!python tags)
20+
args: [--unsafe]
21+
files: ^mkdocs\.yml$
1822
- id: check-toml
1923
- id: check-json
2024
- id: check-merge-conflict

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ npm --prefix web run test # Vitest unit tests
6060
- **Design spec**: `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations)
6161
- **Architecture**: `docs/architecture/` (overview, tech-stack, decision log)
6262
- **Roadmap**: `docs/roadmap/` (status, open questions, future vision)
63+
- **Security**: `docs/security.md` (comprehensive security architecture, hardening, CI/CD security, compliance)
6364
- **Reference**: `docs/reference/` (research, standards)
6465
- **REST API reference**: `docs/rest-api.md` — links to standalone Scalar UI page at `docs/_generated/api-reference.html` (both generated by `scripts/export_openapi.py` in CI)
6566
- **Library reference**: `docs/api/` — auto-generated from docstrings via mkdocstrings + Griffe (AST-based, no imports)
@@ -150,7 +151,7 @@ web/ # Vue 3 + PrimeVue + Tailwind CSS dashboard
150151
- **Every module** with business logic MUST have: `from ai_company.observability import get_logger` then `logger = get_logger(__name__)`
151152
- **Never** use `import logging` / `logging.getLogger()` / `print()` in application code
152153
- **Variable name**: always `logger` (not `_logger`, not `log`)
153-
- **Event names**: always use constants from the domain-specific module under `ai_company.observability.events` (e.g. `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, `CFO_ANOMALY_DETECTED` from `events.cfo`, `CONFLICT_DETECTED` from `events.conflict`, `MEETING_STARTED` from `events.meeting`, `CLASSIFICATION_START` from `events.classification`, `CONSOLIDATION_START` from `events.consolidation`, `ORG_MEMORY_QUERY_START` from `events.org_memory`, `API_REQUEST_STARTED` from `events.api`, `CODE_RUNNER_EXECUTE_START` from `events.code_runner`, `DOCKER_EXECUTE_START` from `events.docker`, `MCP_INVOKE_START` from `events.mcp`, `SECURITY_EVALUATE_START` from `events.security`, `HR_HIRING_REQUEST_CREATED` from `events.hr`, `PERF_METRIC_RECORDED` from `events.performance`, `TRUST_EVALUATE_START` from `events.trust`, `PROMOTION_EVALUATE_START` from `events.promotion`, `PROMPT_BUILD_START` from `events.prompt`, `MEMORY_RETRIEVAL_START` from `events.memory`, `MEMORY_BACKEND_CONNECTED` from `events.memory`, `MEMORY_ENTRY_STORED` from `events.memory`, `MEMORY_BACKEND_SYSTEM_ERROR` from `events.memory`, `AUTONOMY_ACTION_AUTO_APPROVED` from `events.autonomy`, `TIMEOUT_POLICY_EVALUATED` from `events.timeout`, `PERSISTENCE_AUDIT_ENTRY_SAVED` from `events.persistence`, `TASK_ENGINE_STARTED` from `events.task_engine`, `COORDINATION_STARTED` from `events.coordination`, `COMMUNICATION_DISPATCH_START` from `events.communication`, `COMPANY_STARTED` from `events.company`, `CONFIG_LOADED` from `events.config`, `CORRELATION_ID_CREATED` from `events.correlation`, `DECOMPOSITION_STARTED` from `events.decomposition`, `DELEGATION_STARTED` from `events.delegation`, `EXECUTION_LOOP_STARTED` from `events.execution`, `GIT_OPERATION_START` from `events.git`, `PARALLEL_EXECUTION_STARTED` from `events.parallel`, `PERSONALITY_LOADED` from `events.personality`, `QUOTA_CHECKED` from `events.quota`, `ROLE_ASSIGNED` from `events.role`, `ROUTING_STARTED` from `events.routing`, `SANDBOX_EXECUTE_START` from `events.sandbox`, `TASK_CREATED` from `events.task`, `TASK_ASSIGNMENT_STARTED` from `events.task_assignment`, `TASK_ROUTING_STARTED` from `events.task_routing`, `TEMPLATE_LOADED` from `events.template`, `TOOL_INVOKE_START` from `events.tool`, `WORKSPACE_CREATED` from `events.workspace`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`
154+
- **Event names**: always use constants from the domain-specific module under `ai_company.observability.events` (e.g. `PROVIDER_CALL_START` from `events.provider`, `BUDGET_RECORD_ADDED` from `events.budget`, `CFO_ANOMALY_DETECTED` from `events.cfo`, `CONFLICT_DETECTED` from `events.conflict`, `MEETING_STARTED` from `events.meeting`, `CLASSIFICATION_START` from `events.classification`, `CONSOLIDATION_START` from `events.consolidation`, `ORG_MEMORY_QUERY_START` from `events.org_memory`, `API_REQUEST_STARTED` from `events.api`, `API_ROUTE_NOT_FOUND` from `events.api`, `CODE_RUNNER_EXECUTE_START` from `events.code_runner`, `DOCKER_EXECUTE_START` from `events.docker`, `MCP_INVOKE_START` from `events.mcp`, `SECURITY_EVALUATE_START` from `events.security`, `HR_HIRING_REQUEST_CREATED` from `events.hr`, `PERF_METRIC_RECORDED` from `events.performance`, `TRUST_EVALUATE_START` from `events.trust`, `PROMOTION_EVALUATE_START` from `events.promotion`, `PROMPT_BUILD_START` from `events.prompt`, `MEMORY_RETRIEVAL_START` from `events.memory`, `MEMORY_BACKEND_CONNECTED` from `events.memory`, `MEMORY_ENTRY_STORED` from `events.memory`, `MEMORY_BACKEND_SYSTEM_ERROR` from `events.memory`, `AUTONOMY_ACTION_AUTO_APPROVED` from `events.autonomy`, `TIMEOUT_POLICY_EVALUATED` from `events.timeout`, `PERSISTENCE_AUDIT_ENTRY_SAVED` from `events.persistence`, `TASK_ENGINE_STARTED` from `events.task_engine`, `COORDINATION_STARTED` from `events.coordination`, `COMMUNICATION_DISPATCH_START` from `events.communication`, `COMPANY_STARTED` from `events.company`, `CONFIG_LOADED` from `events.config`, `CORRELATION_ID_CREATED` from `events.correlation`, `DECOMPOSITION_STARTED` from `events.decomposition`, `DELEGATION_STARTED` from `events.delegation`, `EXECUTION_LOOP_STARTED` from `events.execution`, `GIT_OPERATION_START` from `events.git`, `PARALLEL_EXECUTION_STARTED` from `events.parallel`, `PERSONALITY_LOADED` from `events.personality`, `QUOTA_CHECKED` from `events.quota`, `ROLE_ASSIGNED` from `events.role`, `ROUTING_STARTED` from `events.routing`, `SANDBOX_EXECUTE_START` from `events.sandbox`, `TASK_CREATED` from `events.task`, `TASK_ASSIGNMENT_STARTED` from `events.task_assignment`, `TASK_ROUTING_STARTED` from `events.task_routing`, `TEMPLATE_LOADED` from `events.template`, `TOOL_INVOKE_START` from `events.tool`, `WORKSPACE_CREATED` from `events.workspace`). Import directly: `from ai_company.observability.events.<domain> import EVENT_CONSTANT`
154155
- **Structured kwargs**: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`
155156
- **All error paths** must log at WARNING or ERROR with context before raising
156157
- **All state transitions** must log at INFO

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ graph TB
121121
| [Architecture](docs/architecture/index.md) | System overview, tech stack, decision log |
122122
| [API Reference](docs/rest-api.md) | REST API reference (Scalar/OpenAPI) |
123123
| [Library Reference](docs/api/index.md) | Auto-generated from docstrings |
124+
| [Security](docs/security.md) | Security architecture, hardening, CI/CD security |
124125
| [Developer Setup](docs/getting_started.md) | Clone, test, lint, contribute |
125126
| [User Guide](docs/user_guide.md) | Install, configure, run via Docker |
126127

docker/sandbox/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
FROM node:22-slim AS node-base
1+
FROM node:22-slim@sha256:9c2c405e3ff9b9afb2873232d24bb06367d649aa3e6259cbe314da59578e81e9 AS node-base
22

3-
FROM python:3.14-slim
3+
FROM python:3.14.3-slim@sha256:6a27522252aef8432841f224d9baaa6e9fce07b07584154fa0b9a96603af7456
44

55
COPY --from=node-base /usr/local/bin/node /usr/local/bin/node
66
COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules

docs/api/api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
---
2+
search:
3+
exclude: true
4+
---
5+
16
# API Layer
27

38
Litestar REST + WebSocket API — controllers, authentication, guards, and channels.

docs/api/budget.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
---
2+
search:
3+
exclude: true
4+
---
5+
16
# Budget
27

38
Cost tracking, budget enforcement, auto-downgrade, quota management, and CFO optimization.

docs/api/communication.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
---
2+
search:
3+
exclude: true
4+
---
5+
16
# Communication
27

38
Inter-agent messaging — bus, dispatcher, delegation, loop prevention, conflict resolution, and meeting protocols.

0 commit comments

Comments
 (0)