Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
- name: Export locked runtime dependencies
Expand All @@ -30,7 +30,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install uv
Expand All @@ -43,7 +43,7 @@ jobs:
run: uv run pytest
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: coverage.xml
Expand All @@ -52,7 +52,7 @@ jobs:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Download coverage report
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v8.3.2
- name: Build the docs site
run: uv run --group docs mkdocs build --strict
- name: Upload the site artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v5
with:
path: site

Expand All @@ -42,4 +42,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8
- name: Build sdist + wheel
run: uv build --out-dir dist
- name: Publish to PyPI (OIDC trusted publishing)
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ EXPOSE 8000

# Apply migrations, then serve with gunicorn. Provide DATABASE_URL and
# ACES_WORKBENCH_SECRET_KEY via the environment.
CMD ["sh", "-c", "aces-workbench migrate --noinput && gunicorn aces_scenario_workbench.wsgi:application --bind 0.0.0.0:8000 --workers 3"]
CMD ["sh", "-c", "aces-workbench migrate --noinput && gunicorn aces_scenario_workbench.wsgi:application --bind 0.0.0.0:${PORT:-8000} --workers ${WEB_CONCURRENCY:-3}"]
32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ aces-workbench serve # http://127.0.0.1:8000

Before the first PyPI release, install from a source checkout with
`pip install .`. See [`docs/setup.md`](docs/setup.md) for the full setup
walkthrough (projects, invitations, email, password reset).
walkthrough (scenarios, invitations, email, password reset).

By default the workbench uses a local SQLite database (`db.sqlite3` in the
current directory). Point it at PostgreSQL by setting `DATABASE_URL`:
Expand All @@ -34,7 +34,7 @@ export DATABASE_URL=postgres://user:pass@localhost:5432/workbench
```

The Django admin defaults to `/control/` and is available to administrators for
creating projects and managing membership. Set `ACES_WORKBENCH_ADMIN_PATH` to
creating scenarios and managing membership. Set `ACES_WORKBENCH_ADMIN_PATH` to
use a deployment-specific admin path.

## Documentation
Expand Down Expand Up @@ -90,14 +90,27 @@ restricted to administrators.
aces-workbench serve [--host H] [--port P] # run the app (applies migrations first)
aces-workbench migrate # apply database migrations
aces-workbench createadmin # create an administrator account
aces-workbench import <path> --project <slug> # import a pack's ATLAS projection
aces-workbench import <path> --scenario <slug> # import a pack's ATLAS projection
aces-workbench sync <path> [--slug <slug>] # create/update a scenario and import it
aces-workbench doctor # report configuration readiness
aces-workbench manage <command> [...] # any Django management command
```

Administrators create projects and invite members from the Django admin
Administrators create scenarios and invite members from the Django admin
(`/control/` by default); import a scenario pack's ATLAS technique projection with
`aces-workbench import <pack-path> --project <slug>`.
`aces-workbench import <pack-path> --scenario <slug>`.

For local dogfooding against an actively developed pack, use `sync`. It creates
the scenario row when needed, updates explicit scenario metadata, imports the
current projection as a digest-addressed immutable revision, and does not write
back into the pack. When challenge, flag placement, objective, telemetry, and
runtime challenge contracts are present, `sync` also imports implemented
challenges and their evidence requirements into the revision:

```bash
aces-workbench sync <pack-path> --slug <slug> --name "Display Name" \
--grant-user you@example.com --role administrator
```

## Development

Expand All @@ -109,6 +122,15 @@ uv run ruff format . # format
make hooks # activate commit-time hooks (once per clone)
```

The authenticated workspace shell is a Vite/React SPA. Rebuild the Django-served
assets after frontend changes:

```bash
cd frontend
npm install
npm run build
```

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the developer workflow.

## Deployment
Expand Down
12 changes: 6 additions & 6 deletions docs/access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ In the Django admin (`/control/` by default, unless `ACES_WORKBENCH_ADMIN_PATH`
is set):

1. Go to **Invitations → Add**.
2. Enter the person's **email**, the **project**, and their **role**.
2. Enter the person's **email**, the **scenario**, and their **role**.
3. Save. An email with an accept link is sent automatically.

The accept link:
Expand All @@ -34,9 +34,9 @@ again. (With the default console email backend the message prints to the server
console instead of being delivered — configure SMTP to send for real; see
[setup](setup.md).)

## Projects, membership, and roles
## Scenarios, membership, and roles

Access is scoped per project: a signed-in user only sees and acts on projects
Access is scoped per scenario: a signed-in user only sees and acts on scenarios
they are a member of. This is enforced on the server — hidden navigation is never
treated as a substitute for authorization.

Expand All @@ -49,9 +49,9 @@ Each membership carries a role:
| Administrator | Yes | Yes |
| Stakeholder | Yes | No (read-only) |

To grant an existing user access to another project, add a **Membership** (Admin
To grant an existing user access to another scenario, add a **Membership** (Admin
→ Memberships → Add) with the appropriate role, or send them an invitation to
that project.
that scenario.

## Forgotten passwords

Expand All @@ -61,7 +61,7 @@ delivery being configured.

## Removing access

- Delete a user's **Membership** to remove their access to a single project.
- Delete a user's **Membership** to remove their access to a single scenario.
- Deactivate or delete the **User** in the admin to revoke access entirely.
- A signed-in user can export or delete **their own** account and content from
the account page (`/me/`).
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The repository already has important boundaries that must remain intact:

- ACES packs and review bundles are the source of scenario content; the
workbench database owns collaboration state only.
- Project membership is the authorization boundary, enforced in
- Scenario membership is the authorization boundary, enforced in
`workbench.access` and `workbench.authz`.
- Account creation is invite-only through the existing accounts app.
- Security posture is centralized in Django settings, middleware, CSP,
Expand Down Expand Up @@ -72,7 +72,7 @@ Security gates the implementation must pass:
- Authentication: reuse Django auth views, the custom email user model,
password validators, django-axes, and django-ratelimit. Do not create open
registration or parallel login/reset flows.
- Authorization: use `login_required`, `access.member_project`,
- Authorization: use `login_required`, `access.member_scenario`,
`access.scoped_revision`, and `authz.can_contribute`; hiding links is not
authorization.
- CSRF and destructive actions: account deletion and all state-changing forms
Expand All @@ -99,7 +99,7 @@ Canonical incumbents to build on:
- Readiness reporting in `workbench.management.commands.doctor`.
- URL names from `accounts.urls`, `workbench.urls`, and Django `admin:index`.
- Account forms/views in `accounts.forms` and `accounts.views`.
- Project authorization helpers in `workbench.access` and `workbench.authz`.
- Scenario authorization helpers in `workbench.access` and `workbench.authz`.
- Collaboration context/actions in `workbench.collab`.
- Projection parsing/import rules in `workbench.ingest`.
- Existing test suites for hardening, auth, accounts, review views, collab,
Expand Down
2 changes: 1 addition & 1 deletion docs/go-live-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Work through this before exposing an instance to the internet. See
- [ ] Migrations are applied (`aces-workbench migrate`).
- [ ] The first administrator exists (`aces-workbench createadmin`).
- [ ] The configured Django admin path is restricted to administrators.
- [ ] Sign in over HTTPS, create a project, and send yourself an invitation to
- [ ] Sign in over HTTPS, create a scenario, and send yourself an invitation to
confirm email delivery end to end.

## Operate
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ writes changes back into a pack.
hosting. Check this first so nothing blocks you halfway through.
- **[Setup](setup.md)** — install to a working, multi-user workspace.
- **[Access & registration](access-control.md)** — the invite-only account model
and project roles.
and scenario roles.
- **[Deployment](deployment.md)** — container, configuration, and hosting.
- **[Go-live checklist](go-live-checklist.md)** — the final checks before you
expose an instance to the internet.
Expand Down
4 changes: 2 additions & 2 deletions docs/product-intent.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ Hiding navigation controls in the browser is not authorization. Access events an
review-state changes should be auditable.

The initial roles are `author`, `reviewer`, `stakeholder`, and `administrator`.
Authorization must be enforceable per scenario or project, not only per deployment.
Authorization must be enforceable per scenario, not only per deployment.

## Initial Implementation Slices

1. Define and validate a versioned review-bundle schema from a sample ATLAS projection.
2. Select the application stack and establish authenticated users and projects.
2. Select the application stack and establish authenticated users and scenarios.
3. Ingest immutable scenario revisions and render the prototype overview.
4. Add stable detail routes for every object and relationship type.
5. Add anchored comments, review states, and an auditable activity stream.
Expand Down
10 changes: 5 additions & 5 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ debug status so you can see what still needs configuration.
Set `ACES_WORKBENCH_SECRET_KEY` to a stable value; without it a random key is
used and sessions reset on every restart.

## 3. Create projects and add people
## 3. Create scenarios and add people

Sign in at `/accounts/login/` and open the Django admin at `/control/` unless
you changed `ACES_WORKBENCH_ADMIN_PATH`.

- **Create a project**: Admin → Projects → Add.
- **Create a scenario**: Admin → Scenarios → Add.
- **Add a member you can manage directly**: Admin → Users → Add (this sets a
real password), then add a Membership on the project with a role
real password), then add a Membership on the scenario with a role
(`author`, `reviewer`, `stakeholder`, `administrator`).
- **Invite by email**: Admin → Invitations → Add (email, project, role). An
- **Invite by email**: Admin → Invitations → Add (email, scenario, role). An
invitation email with an accept link is sent on save; the invitee sets their
own password. Use the "Resend invitation email" action to send it again.

Expand All @@ -55,7 +55,7 @@ from there for local use. Configure SMTP (below) to deliver for real.
## 4. Import a scenario

```bash
aces-workbench import /path/to/pack --project <project-slug>
aces-workbench import /path/to/pack --scenario <scenario-slug>
```

Point it at a pack directory containing an ATLAS technique projection, or at the
Expand Down
9 changes: 9 additions & 0 deletions fixtures/sample-scenario/assets/affordances.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
schema_version: 1
pack: sample-scenario
source_oracle: oracle/objectives.yaml
affordances:
- id: proof-sink
title: Proof sink
type: evidence
asset: proof-service
description: Receives synthetic evidence receipts.
12 changes: 12 additions & 0 deletions fixtures/sample-scenario/assets/planned-assets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
schema_version: 1
pack: sample-scenario
description: Minimal planned asset fixture.
asset_sets:
- asset_id: proof-service
category: evidence
implementation_status: implemented_source
paths: [fixtures/sample-scenario/oracle/telemetry.yaml]
visibility: participant-visible
source_refs: [proof-api]
topology_refs: [proof-service]
implementation_plan: Keep proof events synthetic for fixture tests.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INFER_PATH = "/v1/infer"

QUICK_CHALLENGES = ({"id": "recon", "title": "Recon", "entrypoint": INFER_PATH},)
20 changes: 20 additions & 0 deletions fixtures/sample-scenario/challenges/challenges.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
schema_version: 1
pack: sample-scenario
challenges:
- flag_id: flag-recon
title: Recon Receipt
category: Model Security
difficulty: easy
points: 100
question: Submit evidence that the reconnaissance behavior was completed.
hints:
- Compare the candidate action with the expected evidence event.
- The receipt must be tied to the participant namespace.
- flag_id: flag-evasion
title: Evasion Receipt
category: Model Security
difficulty: medium
points: 150
question: Submit evidence that the evasion behavior is planned.
hints:
- This challenge is planned but not runtime-backed in the sample fixture.
9 changes: 9 additions & 0 deletions fixtures/sample-scenario/design/software-inventory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
schema_version: 1
components:
- component_id: proof-api
topology_refs: [proof-service]
upstream: local
operating_mode: synthetic
profiles: [local]
authenticity: fixture
path_critical: true
41 changes: 41 additions & 0 deletions fixtures/sample-scenario/design/topology.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
schema_version: 1
name: sample-topology
profiles:
local: {}
zones:
- name: participant
kind: user
networks:
- name: lab
zone: participant
assets:
- id: proof-service
hostname: proof.local
asset_type: service-host
role: evidence collector
zone: participant
networks: [lab]
software_component: proof-api
visibility: participant-visible
reset_owner: proof-service
implementation_status: implemented
description: Receives synthetic proof events.
services:
- id: proof-api
asset: proof-service
ports: [8080]
software_component: proof-api
visibility: participant-visible
reset_owner: proof-service
description: Proof API endpoint.
applications: []
datasets: []
artifacts: []
path_objectives:
- id: obj-sample
title: Sample path
success_states: [S-RECON, S-EVASION]
validation_flows:
- id: validate-sample
command: validation/validate_contract.py
covers: [obj-sample]
29 changes: 29 additions & 0 deletions fixtures/sample-scenario/flags/placement.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
schema_version: 1
pack: sample-scenario
source_scoring: oracle/scoring.yaml
namespace: [range_instance, participant]
flags:
- flag_id: flag-recon
outcome: recon
source: instructions
instructions: docs/attack-path.md#module-1
profiles: [local]
evidence: ev-recon
delivery:
kind: verdict_response
asset: proof-service
service: proof-api
affordance: proof-sink
reset_owner: proof-service
- flag_id: flag-evasion
outcome: evasion
source: instructions
instructions: docs/attack-path.md#module-2
profiles: [local]
evidence: ev-evasion
delivery:
kind: verdict_response
asset: proof-service
service: proof-api
affordance: proof-sink
reset_owner: proof-service
Loading
Loading