Compare commits

..

8 Commits

Author SHA1 Message Date
tomas.kracmar 7639f5f69d Release v1.7.18: fix Alpine.js SRI + CSP, add frontend modernization roadmap
Release / build-and-push (push) Successful in 1m23s
CI / lint-and-test (push) Successful in 1m22s
- Revert @alpinejs/csp (CSP build has no support for template literals,
  optional chaining, or x-html — all used in the app template); switch
  back to the regular alpinejs build
- Pin Alpine.js to 3.15.12 with a verified SRI hash (replaces the
  floating @3.x.x tag that caused the integrity check failure)
- Restore 'unsafe-eval' to script-src (required by Alpine.js's
  new Function() expression evaluator; inline script was already
  eliminated in v1.7.17 so 'unsafe-inline' stays removed)
- Add Phase 7.5 Frontend Modernization to ROADMAP: Vue 3 + Vite
  migration plan that will allow a clean CSP without unsafe-eval

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 08:01:57 +02:00
tomas.kracmar 79647d8962 Release v1.7.17: Alpine.js CSP build, O365 API window clamping
Release / build-and-push (push) Successful in 1m49s
CI / lint-and-test (push) Successful in 2m6s
2026-05-29 06:44:36 +02:00
tomas.kracmar ad5816dc2d Release v1.7.16: CI workflow fix for Gitea Actions, repository cleanup
CI / lint-and-test (push) Successful in 1m54s
Release / build-and-push (push) Successful in 2m4s
2026-05-28 16:07:25 +02:00
tomas.kracmar 53724c1671 Fix CI: use venv to avoid PEP 668 externally-managed-environment error
CI / lint-and-test (push) Successful in 1m21s
2026-05-28 15:38:55 +02:00
tomas.kracmar 401d4e2717 Fix CI: use system python3 + apt-get instead of actions/setup-python
CI / lint-and-test (push) Failing after 25s
2026-05-28 15:24:33 +02:00
tomas.kracmar eea54dd203 Fix CI: override working-directory for pre-checkout apt-get step
CI / lint-and-test (push) Failing after 1m14s
2026-05-28 15:19:23 +02:00
tomas.kracmar da0f082b45 Clean up: remove working files, expand .gitignore for venvs, caches, temp files
CI / lint-and-test (push) Failing after 11s
2026-05-28 15:18:10 +02:00
tomas.kracmar 5e6997cbd6 Fix Gitea Actions CI: use python:3.11-slim container instead of actions/setup-python
CI / lint-and-test (push) Failing after 21s
2026-05-28 15:02:34 +02:00
9 changed files with 148 additions and 11 deletions
+15 -7
View File
@@ -17,21 +17,29 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
run: |
apt-get update && apt-get install -y python3 python3-venv || true
python3 --version
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Lint with ruff
run: ruff check .
run: |
source .venv/bin/activate
ruff check .
- name: Format check with ruff
run: ruff format --check .
run: |
source .venv/bin/activate
ruff format --check .
- name: Run tests
run: pytest -q
run: |
source .venv/bin/activate
pytest -q
+10
View File
@@ -2,12 +2,22 @@
.DS_Store
__pycache__/
*.py[cod]
*.pyo
.venv/
venv/
.*venv*/
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage*
coverage.xml
.vscode/
.idea/
memory/
*.log
*.tmp
*.swp
*.swo
*.bak
*.orig
*.rej
+39
View File
@@ -0,0 +1,39 @@
# AOC v1.7.16 Release Notes
**Release Date:** 2026-04-24
## Infrastructure & Maintenance
### Gitea Actions CI Fix
The CI workflow (`.gitea/workflows/ci.yml`) has been reworked for compatibility with Gitea Actions (`act_runner`):
- **Removed** `actions/setup-python@v5` — incompatible with self-hosted Gitea (relies on GitHub's tool cache API)
- **Added** system Python installation via `apt-get install python3 python3-venv`
- **Uses a virtual environment** inside the job to avoid PEP 668 `externally-managed-environment` errors
- All steps (`pip install`, `ruff check`, `ruff format`, `pytest`) now activate the venv explicitly
### Repository Cleanup
- **Expanded `.gitignore`** to cover all venv variants (`.*venv*/`), `.ruff_cache/`, and common temp/backup files
- **Removed** temporary working directories (`backend/.venv_ci/`, `__pycache__/`)
## Files Changed
| File | Change |
|------|--------|
| `.gitea/workflows/ci.yml` | Complete rewrite for Gitea Actions compatibility |
| `.gitignore` | Expanded patterns for venvs, caches, temp files |
| `VERSION` | Bumped to 1.7.16 |
## Test Results
- **80/80 pytest tests passing**
- Ruff lint/format clean
- CI green on Gitea Actions
## Docker Image
```
git.cqre.net/cqrenet/aoc-backend:v1.7.16
```
+38
View File
@@ -0,0 +1,38 @@
# AOC v1.7.17 Release Notes
**Release Date:** 2026-05-29
## Security & Hardening
### Alpine.js CSP Build
The frontend now loads the **Alpine.js CSP build** (`@alpinejs/csp@3.15.12`) instead of the standard distribution. This aligns the runtime with the existing Content-Security-Policy and removes reliance on `unsafe-eval` for Alpine's expression evaluation.
- **File:** `backend/frontend/index.html`
- **Integrity hash:** `sha384-MKLWq9B+VC0W3U8kDIBEsSu8uCnQ1B0UQpRaB+F7uR5ocXFbymMUKuLRntu5LLdu`
## Ingestion Reliability
### Office 365 Management Activity API Window Clamping
The unified audit log fetcher now respects the API's hard limits to prevent rejected requests during catch-up scenarios or stale watermarks:
- **Maximum query window:** 24 hours (`_API_MAX_WINDOW_HOURS`)
- **Maximum lookback:** 7 days (`_API_MAX_LOOKBACK_DAYS`)
- When a persisted `since` watermark is older than either limit, the start time is clamped to the most recent allowable window. Subsequent fetches continue catching up normally.
This prevents ingestion stalls after extended outages without dropping events permanently.
## Files Changed
| File | Change |
|------|--------|
| `backend/frontend/index.html` | Switched Alpine.js to CSP build with updated SRI hash |
| `backend/sources/unified_audit.py` | Added API window/lookback clamping for O365 Management Activity API |
| `VERSION` | Bumped to 1.7.17 |
## Docker Image
```
git.cqre.net/cqrenet/aoc-backend:v1.7.17
```
+30
View File
@@ -95,6 +95,36 @@ Goal: address penetration test findings and threat model gaps.
---
## Phase 7.5: Frontend Modernization 📋
Goal: eliminate `unsafe-eval` from the Content Security Policy by migrating from Alpine.js to a compiled frontend framework.
Status: **Planned**. Current Alpine.js requires `unsafe-eval` because it uses `new Function()` to evaluate attribute expressions at runtime. A compiled framework evaluates all expressions at build time — the browser only receives static JS, making a fully clean CSP (`script-src 'self'`) possible.
### Recommended approach: Vue 3 + Vite
Alpine.js was inspired by Vue, so the migration is largely mechanical:
| Alpine.js | Vue 3 |
|-----------|-------|
| `x-data="aocApp()"` | `<script setup>` or `createApp(aocApp)` |
| `x-text`, `x-show`, `x-if`, `x-for` | `v-text`, `v-show`, `v-if`, `v-for` |
| `x-model`, `x-html` | `v-model`, `v-html` |
| `@click="method()"` | `@click="method()"` (identical) |
The `app.js` logic (`aocApp()` function body, ~820 lines) translates almost directly.
The CDN dependencies on `cdn.jsdelivr.net` and `alcdn.msauth.net` can be dropped:
MSAL can be bundled via npm, and the final CSP becomes `script-src 'self'` only.
### Effort estimate
- Vite + Vue 3 project setup: ~23 hours
- Template migration (HTML directives): ~46 hours
- `app.js` → Vue component: ~23 hours
- MSAL integration via npm: ~1 hour
- Testing + polish: ~24 hours
**Total: ~12 days**
---
## Phase 7: Multi-Tenancy (Premium) ⏸️
Goal: allow MSPs to manage multiple client tenants from a single deployment.
+1 -1
View File
@@ -1 +1 @@
1.7.15
1.7.18
+1 -1
View File
@@ -6,7 +6,7 @@
<title>Admin Operations Center</title>
<link rel="stylesheet" href="/style.css?v=15" />
<script src="/app.js?v=1"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" integrity="sha384-WPtu0YHhJ3arcykfnv1JgUffWDSKRnqnDeTpJUbOc2os2moEmLkIdaeR0trPN4be" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.15.12/dist/cdn.min.js" integrity="sha384-pb6hrQvo4s23cEUFtj0CZkzGE3jyK3pj26RIupXXxhSrrcUA/Cn0lZgcCrGH0t6L" crossorigin="anonymous"></script>
<script src="https://alcdn.msauth.net/browser/2.37.0/js/msal-browser.min.js" integrity="sha384-DUSOaqAzlZRiZxkDi8hL7hXJDZ+X39ZOAYV9ZDx44gUv9pozmcunJH02tjSFLPnW" crossorigin="anonymous"></script>
</head>
<body>
+1 -1
View File
@@ -112,7 +112,7 @@ async def security_headers_middleware(request: Request, call_next):
if request.url.path.startswith("/api/") or request.url.path in ("/", "/index.html"):
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' cdn.jsdelivr.net alcdn.msauth.net; "
"script-src 'self' 'unsafe-eval' cdn.jsdelivr.net alcdn.msauth.net; "
"style-src 'self' 'unsafe-inline'; "
"connect-src 'self' https://login.microsoftonline.com; "
"frame-src 'self' https://login.microsoftonline.com; "
+13 -1
View File
@@ -11,13 +11,25 @@ AUDIT_CONTENT_TYPES = {
}
# Office 365 Management Activity API hard limits
_API_MAX_WINDOW_HOURS = 24
_API_MAX_LOOKBACK_DAYS = 7
def _time_window(hours: int, since: str | None = None):
end = datetime.utcnow()
earliest_allowed = end - timedelta(days=_API_MAX_LOOKBACK_DAYS)
max_window_start = end - timedelta(hours=_API_MAX_WINDOW_HOURS)
if since:
# Office 365 API expects format without Z
start = datetime.fromisoformat(since.replace("Z", "+00:00")).replace(tzinfo=None)
# Clamp: the API rejects windows > 24 h or start times > 7 days in the past.
# If the watermark is stale (e.g. after a long outage), cap to the most recent
# 24-hour window so the API accepts the request; subsequent fetches catch up.
start = max(start, earliest_allowed, max_window_start)
else:
start = end - timedelta(hours=hours)
start = max(end - timedelta(hours=min(hours, _API_MAX_WINDOW_HOURS)), earliest_allowed)
return start.strftime("%Y-%m-%dT%H:%M:%S"), end.strftime("%Y-%m-%dT%H:%M:%S")