Skip to content
Closed
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
4 changes: 2 additions & 2 deletions .github/workflows/agent-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,9 @@ jobs:
: "${POSTGRES_USER:=ctops}"
: "${POSTGRES_DB:=ctops}"
TIERS=$(docker compose exec -T db psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At \
-c "SELECT name || ': ' || licence_tier FROM organisations WHERE deleted_at IS NULL ORDER BY created_at;" 2>/dev/null || true)
-c "SELECT name || ': ' || licence_tier FROM instance_settings WHERE deleted_at IS NULL ORDER BY created_at;" 2>/dev/null || true)
if [ -z "$TIERS" ]; then
echo "Licence tier: unknown (no organisation configured yet)"
echo "Licence tier: unknown (no instance configured yet)"
else
echo "Licence tier:"
while IFS= read -r line; do printf ' %s\n' "$line"; done <<< "$TIERS"
Expand Down
18 changes: 9 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export type NewHost = typeof hosts.$inferInsert
// Use `unknown` and narrow, or define proper types

// Server Actions must be explicitly typed
export async function getHosts(orgId: string): Promise<Host[]> {}
export async function getHosts(instanceId: string): Promise<Host[]> {}

// All server actions live in apps/web/lib/actions/
// Named by domain: hosts.ts, certificates.ts, alerts.ts etc.
Expand All @@ -107,8 +107,8 @@ export async function getHosts(orgId: string): Promise<Host[]> {}
```typescript
// Reading data — TanStack Query calling a Server Action
const { data, isLoading } = useQuery({
queryKey: ['hosts', orgId],
queryFn: () => getHosts(orgId),
queryKey: ['hosts', instanceId],
queryFn: () => getHosts(instanceId),
})

// Writing data — TanStack Query mutation calling a Server Action
Expand All @@ -131,7 +131,7 @@ All exported from `apps/web/lib/db/schema/index.ts`
Every table MUST have:
```typescript
id: text('id').primaryKey().$defaultFn(() => createId()), // cuid2
organisationId: text('organisation_id').notNull().references(() => organisations.id),
instanceId: text('instance_id').notNull().references(() => instances.id),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
deletedAt: timestamp('deleted_at', { withTimezone: true }), // soft delete
Expand All @@ -148,7 +148,7 @@ Resources with state use both a current status column AND a status history table
Never overwrite status — append to history.

### Foundational patterns (always respect these)
1. **Multi-tenancy** — every resource row has `organisation_id`
1. **Instance scoping** — every resource row has `instance_id`
2. **Soft deletes** — `deleted_at` on every table, never hard delete
3. **Tags as first-class** — flexible `key:value` tagging on any resource
4. **Event spine** — significant state changes append to the `events` table
Expand Down Expand Up @@ -179,7 +179,7 @@ Note: LDAP/AD is in the community tier — paywalling it would block corporate a
**RBAC Roles:**
```
super_admin Full system access, billing, global config
org_admin User/team management, integrations, org config
instance_admin User/team management, integrations, instance config
engineer Read/write on assigned resource groups, alert management
read_only View dashboards, alerts, inventory (management, auditors)
agent Non-human, register host + submit metrics only
Expand Down Expand Up @@ -316,14 +316,14 @@ Include `contextHolder` in component JSX.
## Development Roadmap Phases

```
Phase 0 — Foundation Monorepo, Next.js, auth, orgs, Docker Compose
Phase 0 — Foundation Monorepo, Next.js, auth, instances, Docker Compose
Phase 1 — Agent & Inventory Agents, hosts, basic metrics, gRPC, ingest
Phase 2 — Monitoring Checks, alerting, graphs, TimescaleDB
Phase 3 — Certificates Discovery, expiry alerts, CSR workflows
Phase 4 — Service Accounts Identity, SSH keys, LDAP sync
Phase 5 — Tooling Air-gap bundlers, runbooks, scheduled tasks
Phase 6 — Enterprise SSO, audit log, advanced RBAC, compliance packs
Phase 7 — Cloud SaaS Multi-tenant hardening, billing, hosted offering
Phase 7 — Cloud SaaS Instance-scoped hardening, billing, hosted offering
```

Each phase must be **independently deployable and useful** before the next begins.
Expand Down Expand Up @@ -380,7 +380,7 @@ The docs site is built with **VuePress 2** (`@vuepress/theme-default`). The `app
- No linting errors — fix before committing
- Run `pnpm run build` after every significant change and fix all errors
- Server Actions must validate input with Zod before touching the database
- All database queries must be scoped by `organisationId`
- All database queries must be scoped by `instanceId`
- Never expose internal IDs in URLs without verifying the requesting user has access
- All mutations must be wrapped in try/catch with meaningful error messages

Expand Down
41 changes: 33 additions & 8 deletions ORGANISATION_REMOVAL_TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1098,19 +1098,42 @@ and deploy validation scope.

## Task 16 - Final Residue Sweep And Validation

Status: Not started
Status: Completed

Completed by:
Completed by: Codex

PR:
PR: Pending

Summary:
Summary: Removed the remaining old organisation naming from active web, agent,
ingest, workflow, docs, tests, integration contracts, and launch assertions.
Renamed the remaining role/string residue to instance terminology, added a data
migration for existing `org_admin` values, changed CT-CVE and password-manager
contracts to instance-shaped claims, removed the legacy
`CT_OPS_ORG_TOKEN` fallback, and corrected stale test/doc residue found by the
final scan.

Files changed:
Files changed: `.github/workflows/agent-release.yml`; `agent/**`;
`apps/ingest/**`; `apps/web/**`; `apps/docs/**`; `docs/**`; top-level project
docs and trackers.

Validation:

Follow-up:
- `pnpm --dir apps/web db:validate` — passed.
- `pnpm --dir apps/web type-check` — passed.
- `pnpm --dir apps/web lint` — passed with existing warnings only.
- `pnpm --dir apps/web test:unit` — passed, 348 tests.
- `pnpm --dir apps/web exec playwright test --list` — passed, 125 tests listed.
- `BETTER_AUTH_URL=http://localhost:3100 E2E_DISABLE_AGENT_CACHE_PREWARM=1 pnpm --dir apps/web exec playwright test settings/activation-token.spec.ts settings/agent-enrolment.spec.ts --project=chromium` — blocked by local environment: `POSTGRES_PASSWORD environment variable is required when DATABASE_URL is not set`.
- `pnpm --dir apps/docs build` — passed.
- `go test ./...` in `agent` — passed.
- `go test ./...` in `apps/ingest` — passed.
- `rg -n "orgId|organisationId|organisation_id|org_id|organisations|org_admin|CT_OPS_ORG_TOKEN|installOrg|installOrganisation|ct_ops_organization|org_scope_mismatch|orgSlug|tenant" apps/web apps/ingest agent .github docs apps/docs README.md SECURITY.md CLAUDE.md TASK.md PROGRESS.md PENTEST.md --glob '!**/node_modules/**' --glob '!apps/web/lib/db/migrations/0007_instance_role_residue.sql' --glob '!apps/web/lib/db/migrations/meta/**'` — no matches.
- Broad residue scan excluding the tracker and the transitional migration only
reports the allowed matches recorded below.

Follow-up: Coordinate the activation-token field rename with `ct-portal` before
shipping the purchase flow if the portal has not already been updated to accept
`installInstanceId` / `installInstanceName`.

### Required work

Expand Down Expand Up @@ -1144,7 +1167,9 @@ Follow-up:

## Remaining Allowed Matches

Record any allowed residue here during Task 4. Leave this empty until then.

| Path | Match | Reason |
| --- | --- | --- |
| `apps/web/lib/db/migrations/0007_instance_role_residue.sql` | `org_admin` | Transitional data migration that converts persisted legacy roles to `instance_admin`. |
| `apps/web/lib/certificates/fetch.ts`, certificate checker UI/tests | `organization`, `organizationalUnit`, `issuerOrganization`, `Organization` | X.509 certificate subject/issuer field names and UI labels, unrelated to CT-Ops legacy organisations. |
| `pnpm-lock.yaml` | `Org` inside integrity hashes | Base64 package integrity data, not source or product terminology. |
| `apps/web/app/(dashboard)/hosts/groups/groups-client.tsx` | `Organise` | Plain English UI copy, not organisation identity or schema. |
6 changes: 3 additions & 3 deletions PENTEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ permission to test them:
- Protocol definitions and shared packages under `proto/` and `packages/`
- Default security surfaces shipped by the project, including authentication,
session handling, authorisation, agent enrolment, certificate tooling,
notification delivery, and tenant isolation
notification delivery, and instance isolation

## Allowed Test Activities

The following test categories are in scope when executed carefully and within
the rules below:

- Authentication, session, and access-control testing
- Tenant-boundary and organisation-isolation validation
- Instance-boundary validation validation
- Input validation, injection, SSRF, deserialisation, and file-handling tests
- Deployment and container-hardening review
- Agent, ingest, and API protocol security testing
Expand Down Expand Up @@ -81,7 +81,7 @@ targets and activities are out of scope:
- Physical attacks, phishing, vishing, pretexting, or credential stuffing
- Volumetric denial-of-service, resource-exhaustion floods, or destructive
availability testing
- Any attempt to access data belonging to other users, customers, or tenants
- Any attempt to access data belonging to other users, customers, or instances
beyond the minimum proof needed to demonstrate isolation failure
- Attacks that require already having root or equivalent privileged access on a
host, unless the assessment is specifically scoped to post-compromise impact
Expand Down
Loading
Loading