Quick Reference
A quick reference guide for common tasks and features in the SCF Controls Platform.
Keyboard Shortcuts
Section titled “Keyboard Shortcuts”| Shortcut | Action |
|---|---|
R | Refresh data (same as the refresh button) |
← → | Previous / next record on a detail page — controls, scoping, evidence, tasks, systems, vendors |
Esc | Close a modal, or go back from a detail page to its list |
Enter / Space | Activate the focused list row |
Ctrl+Shift+R / Cmd+Shift+R | Browser hard refresh (clears the cache) |
Navigation
Section titled “Navigation”Sidebar Icons
Section titled “Sidebar Icons”| Section Group | Section | Purpose |
|---|---|---|
| Overview | Dashboard | Compliance posture overview |
| Overview | Analytics | Capability Posture by KSI theme |
| Controls & Frameworks | Control Library | Browse all 1,451 SCF controls |
| Controls & Frameworks | Framework Mappings | Framework-to-control mapping matrix |
| Controls & Frameworks | Control Scoping | Scope controls and identify gaps |
| Risk & Third Party | Risk Register | Risk assessment and 5x5 matrix |
| Risk & Third Party | Vendor Inventory | Third-party vendor management |
| Evidence | Evidence | Unified evidence workspace |
| Operations | Task Management | Track evidence collection tasks |
| Operations | Systems Registry | Manage evidence source systems |
| Operations | User Management | Manage organisation members, roles and teams |
| Admin | Engagements | Client engagement management |
| Admin | Webhooks | Webhook integrations |
| Admin | Audit Log | Field-level change audit trail |
| Admin | Consultant Portal | Multi-client management |
| Admin | Org Settings | Organisation configuration |
Data Refresh
Section titled “Data Refresh”Change detection
Section titled “Change detection”- Polls the server for a change notice every 20 seconds — it does not refetch your data
- The poll stops while the tab is hidden and runs immediately when you return
- Returning to the tab also refetches the queries currently on screen
Manual refresh
Section titled “Manual refresh”- Click the refresh button in the header, or press R
- Browser hard refresh:
Ctrl+Shift+R(Windows) orCmd+Shift+R(Mac)
Header indicator
Section titled “Header indicator”| Indicator | Meaning |
|---|---|
| Updated just now | Refreshed in the last 10 seconds |
| Updated 40s ago / 5m ago / 2h ago | Time since your last refresh |
| Updates available (with a dot) | The poll found a change — refresh to pull it in |
Control Management
Section titled “Control Management”Implementation Status Options
Section titled “Implementation Status Options”| Status | When to Use |
|---|---|
| Not Started | Control scoped but no work begun |
| In Progress | Implementation underway |
| Implemented | Fully operational |
| At Risk | Behind schedule or has issues |
| Not Applicable | Doesn’t apply to your environment |
| Deferred | Intentionally postponed |
Maturity Levels
Section titled “Maturity Levels”| Level | Description |
|---|---|
| Initial | Ad-hoc, inconsistent processes |
| Developing | Repeatable but undocumented |
| Defined | Documented and standardised |
| Managed | Monitored and measured |
| Optimised | Continuously improving |
Priority Levels
Section titled “Priority Levels”| Priority | Meaning |
|---|---|
| Critical | Must address immediately |
| High | Complete soon |
| Medium | Normal priority |
| Low | Address when resources allow |
Evidence Management
Section titled “Evidence Management”Evidence Status Options
Section titled “Evidence Status Options”| Status | Meaning |
|---|---|
| Not Started | No evidence collected yet |
| In Progress | Collection underway |
| Collected | Evidence gathered but not verified |
| Verified | Reviewed and confirmed valid |
| Expired | Past validity period |
| Not Applicable | Doesn’t apply to your environment |
Collection Frequency
Section titled “Collection Frequency”| Frequency | Description |
|---|---|
| One-time | Collect once during initial assessment |
| Monthly | Recurring monthly collection |
| Quarterly | Recurring quarterly collection |
| Annually | Recurring annual collection |
| On-demand | Collect only when specifically requested |
Automation Indicators
Section titled “Automation Indicators”| Icon | Level | Description |
|---|---|---|
| High | Fully automated via API | |
| Medium | Partial automation available | |
| Low | Primarily manual collection |
Task Management
Section titled “Task Management”Task Types
Section titled “Task Types”| Type | Purpose |
|---|---|
| Feasibility | Assess if evidence can be collected as planned |
| Setup | Configure systems for evidence collection |
| Collection | Perform evidence collection activity |
| Review | Review collected evidence for completeness |
| Documentation | Document collection procedures |
| Issue | Address problems with evidence collection |
Task Status Colours
Section titled “Task Status Colours”| Status | Colour | Meaning |
|---|---|---|
| Not Started | Blue | Work not yet begun |
| In Progress | Orange | Currently being worked on |
| Completed | Green | Finished |
Task Ownership
Section titled “Task Ownership”| Rule | Value |
|---|---|
| Owning teams per task | At most one |
| Task with no owning team | Inherits the accountable team of its evidence item |
| Task with an owning team | Overrides the evidence item, for that task only |
| Deleting a team | Its tasks return to inheriting; tasks are never deleted with the team |
| Team from another organisation | Rejected by the database |
A task is atomic — one title, one due date, one doer — so it takes a single owning team rather than the many-to-many assignment that controls and evidence items take. The override exists because Setup, Collection and Review on one evidence item are routinely three different functions.
Notification Recipients
Section titled “Notification Recipients”Every reminder and escalation resolves recipients down the same chain, stopping at the first tier that produces anybody:
| Tier | Recipients |
|---|---|
| 1 | The item’s assignee |
| 2 | The accountable team’s primary and delegate, both, in parallel |
| 3 | Organisation admins |
For a task the chain has one extra step: its own assignee, then its own owning team, then its evidence item’s accountable team, then organisation admins.
| Rule | Value |
|---|---|
| Recipients | A set — assignee who is also team primary gets one notification, not two |
| Consulted (non-accountable) teams | Not on the routine path; reached only on escalation |
| Overdue escalation | On becoming overdue, then +7 days, then +30 days — never once per scheduler run |
| Escalation events | Item overdue; a control’s evidence assessed insufficient; evidence rejected |
| Escalation recipients | The resolved set, plus the primary and delegate of the accountable team and of every consulted team |
| Organisation with no teams | No wider audience than today; tier 2 never resolves |
| Overdue cadence | Changes for every org, teams or not — thresholds replace the daily alert |
See Who Gets Notified for the reasoning behind each of these.
Risk Assessment
Section titled “Risk Assessment”5x5 Risk Matrix
Section titled “5x5 Risk Matrix”Likelihood Scale (1-5):
- 1 = Rare
- 2 = Unlikely
- 3 = Possible
- 4 = Likely
- 5 = Almost Certain
Impact Scale (1-5):
- 1 = Insignificant
- 2 = Minor
- 3 = Moderate
- 4 = Major
- 5 = Severe
Risk Levels
Section titled “Risk Levels”| Score Range | Level | Colour |
|---|---|---|
| 1-4 | Low | Green |
| 5-9 | Medium | Yellow |
| 10-15 | High | Orange |
| 16-25 | Critical | Red |
AI Evidence Assessment
Section titled “AI Evidence Assessment”Two assessment layers, one review queue. The window layer is primary; the per-file layer is diagnostic. Full guide: AI evidence assessment.
Verdict vocabulary
Section titled “Verdict vocabulary”| Level | Values |
|---|---|
| Objective designation (advisory) | appears_satisfied, gap_identified, not_applicable, cannot_assess |
| Window or file status | sufficient, partial, insufficient, unassessable; windows also insufficient_sample |
| Reviewer decision | confirmed, overridden (reason and at least one re-designation required) |
API Endpoints
Section titled “API Endpoints”POST /api/organizations/{org_id}/evidence/{evidence_id}/assess-window editor; queue a window assessmentGET /api/organizations/{org_id}/evidence/{evidence_id}/window-assessments viewer; list windows for an evidence itemGET /api/organizations/{org_id}/evidence/window-assessments/{assessment_id} viewer; one window with ao_findings and file_membershipPOST /api/organizations/{org_id}/evidence/window-assessments/{assessment_id}/verdict/review editor; confirm or override the AI verdictGET /api/organizations/{org_id}/evidence/window-assessments/{assessment_id}/versions viewer; immutable verdict history, newest firstPUT /api/organizations/{org_id}/window-assessments/{ewa_id}/review editor; acceptance review: approved, rejected, needs_revisionGET /api/organizations/{org_id}/evidence/window-assessments/summary viewer; counts by statusPOST /api/organizations/{org_id}/evidence/assess-windows-bulk editor; queue every tracked itemGET /api/organizations/{org_id}/evidence/assessment/review-queue?tier=window|file viewer; verdicts awaiting confirmation (default tier=file)POST /api/organizations/{org_id}/evidence/{evidence_id}/files/{file_id}/assess editor; per-file (diagnostic) assessmentPOST /api/organizations/{org_id}/evidence/{evidence_id}/files/{file_id}/assessment/review editor; confirm or override a per-file verdictGET /api/organizations/{org_id}/evidence/{evidence_id}/files/{file_id}/assessment/versions viewer; per-file verdict historyGET /api/features unauthenticated; the flags the backend is running withPUT …/files/{file_id}/review (per-file document approval) answers 410 Gone for evidence that has
a window assessment while ENABLE_PER_WINDOW_REVIEW is on; the response body points at the window
review endpoint.
User Roles
Section titled “User Roles”| Role | Access Level |
|---|---|
| Admin | Full access: manage users, configure frameworks, view all data |
| Editor | Edit content: manage controls, evidence, tasks, assignments |
| Viewer | Read-only: view controls and reports without editing |
Member Type
Section titled “Member Type”Every organisation membership is recorded as internal or an external contractor. It is a reporting label on the membership — not on the person — and it grants nothing; access stays governed by the organisation role above.
| Rule | Value |
|---|---|
| Values | internal, external_contractor |
| Default | internal, for new members and for every membership that existed before the feature |
| Scope | Per membership — the same person can be internal in one organisation and a contractor in another, and neither organisation sees the other’s record |
| Set by | Admin, from the Type column on the user list row, or Employment Type in the Invite User dialog |
| At invite time | Carried on the invitation alongside the role, and applied to the membership when the invitee accepts |
| Invitation that says nothing | internal — the same way an invitation silent on role means Viewer |
| Changing it later | Any admin, any time, from the member’s row — nothing about it is permanent |
| Visible to | Any organisation member. Non-admins see the value as plain text rather than a dropdown |
| Badge | Reads Contractor. Internal members get no badge; in dropdowns the name is suffixed (Contractor) |
| List filter | All Owner Types / Contractor-owned / Internally owned, on the controls and evidence lists |
| Effect on permissions | None |
| Relationship to role | Independent — changing one never alters the other |
| Inferred from email domain or any existing field | Never — organisations state their own contractors |
| Audit log | Every change made from the member row, one entry per field. Invitations are not audited |
API Endpoints
Section titled “API Endpoints”PATCH /api/organizations/{org_id}/members/{user_id} admin; ?role, ?member_typePOST /api/organizations/{org_id}/invite admin; body: email, role, member_type, messageGET /api/organizations/{org_id}/invites admin; shows what a pending invite will applyPATCH takes query parameters, not a request body — ?member_type=external_contractor alongside
the existing ?role=. Both are optional and at least one must be supplied; sending one leaves the
other untouched.
member_type on the invite body is optional and defaults to internal. It is applied to the
membership at acceptance. The invitee’s public preview of an invitation shows the role only.
Contractor Filter
Section titled “Contractor Filter”GET /api/organizations/{org_id}/scoped-controls-paginated ?accountable_owner_type=internal|external_contractorGET /api/organizations/{org_id}/evidence-tracking ?accountable_owner_type=internal|external_contractorThe same two list endpoints that take ?team_id and ?function_id, resolved through one shared
helper so they cannot answer differently.
The chain is: the assignment marked accountable → that team’s primary member → that person’s type. All three hops are required, so an item with no team assignment, no accountable team, or an accountable team with no primary is absent from the results — it is not “no contractor”, it is the ownership record having no answer. Read an empty result as nothing to report yet.
See User Management for the full guide.
Teams are managed from the Team Management card in User Management. They record ownership; they grant no permissions — access stays governed by the organisation role above.
Functions
Section titled “Functions”Fourteen platform-seeded functions, read-only to organisations:
| Key | Name |
|---|---|
governance_risk_compliance | Governance, Risk & Compliance |
security_operations | Security Operations |
security_engineering | Security Engineering |
it_operations | IT Operations |
software_engineering | Software Engineering / DevSecOps |
identity_access_management | Identity & Access Management |
data_privacy | Data Protection & Privacy |
human_resources | Human Resources |
legal | Legal |
finance | Finance |
procurement_vendor_management | Procurement & Vendor Management |
facilities_physical_security | Facilities & Physical Security |
business_continuity | Business Continuity & Resilience |
executive_leadership | Executive Leadership |
Membership Roles
Section titled “Membership Roles”| Role | Limit per team |
|---|---|
| Primary | At most one |
| Delegate | At most one |
| Member | No limit |
A team with no members, or no primary, is allowed and shows a warning badge.
Team Assignment
Section titled “Team Assignment”Controls and evidence items are assigned owning teams from their detail view. Controls can also be assigned in bulk from the Control Scoping list.
| Rule | Value |
|---|---|
| Owning teams per control or evidence item | No limit |
| Assigning teams in bulk | Controls only — tick rows in Control Scoping and use Assign owner on the selection bar, which claims the accountable team. Evidence items are assigned per item from the detail view |
| Accountable teams per item | At most one |
| Same team assigned twice to one item | Rejected |
| Team from another organisation | Rejected by the database |
| Teams assigned, none accountable | Allowed; shows a No accountable team badge |
| No teams at all | Allowed; no badge |
| Changing assignments | Admin; reading is open to any member |
| Archiving a team | Does not release its assignments |
| Removing a user | Does not change any assignment — assignments name teams, not people |
The free-text Owner Team Label on a control (renamed from Owner Team; values untouched) is a separate legacy field, and nothing converts it into a team assignment. Evidence items have no equivalent label.
API Endpoints
Section titled “API Endpoints”GET /api/functionsGET /api/organizations/{org_id}/teams ?function_id, ?include_inactivePOST /api/organizations/{org_id}/teams adminGET /api/organizations/{org_id}/teams/{team_id} includes members + healthPATCH /api/organizations/{org_id}/teams/{team_id} adminDELETE /api/organizations/{org_id}/teams/{team_id} admin; archives, never destroysGET /api/organizations/{org_id}/teams/{team_id}/membersPOST /api/organizations/{org_id}/teams/{team_id}/members adminPATCH /api/organizations/{org_id}/teams/{team_id}/members/{user_id} adminDELETE /api/organizations/{org_id}/teams/{team_id}/members/{user_id} adminDeleting a team archives it — DELETE sets the team inactive. Use include_inactive on the list
endpoint to see archived teams.
Team assignment of controls and evidence uses one resource for both, selected by type:
GET /api/organizations/{org_id}/team-assignments ?type=control|evidence, ?item_ids, ?team_idPOST /api/organizations/{org_id}/team-assignments admin; {type, item_id, team_id, is_accountable}DELETE /api/organizations/{org_id}/team-assignments/{id} adminThe GET returns one map keyed by item id, with each team and its primary and delegate embedded, so
a list view renders ownership without a request per row. item_ids is repeated, not comma-joined,
and is capped per request; team_id narrows to one team.
POST is an upsert. Assigning a team that is already assigned updates its is_accountable flag and
returns 200; a genuinely new assignment returns 201. Posting is_accountable: true demotes the
current accountable team in the same transaction, so promotion is a single request — never delete
and re-create to move accountability.
item_id is the UUID — scoped_controls.id or evidence_tracking.id — not a control’s scf_id
and not a catalogue evidence_id. An unrecognised type is a 422 naming the supported values; a
mutation attempted by a non-admin is a 403.
Both list endpoints also take team and function filters:
GET /api/organizations/{org_id}/scoped-controls-paginated ?team_id, ?function_idGET /api/organizations/{org_id}/evidence-tracking ?team_id, ?function_idBoth are optional and compose with every other filter on those endpoints (scope_status, domain,
csf_function, framework, search, and system_id on evidence). Supplying both intersects
them — an item must match the team and the function.
They match any assigned team, not only the accountable one, so filtering by a team returns
everything that team is on. Both endpoints resolve that through one shared helper
(services/team_assignments.py::team_assignment_filter()), so the two lists cannot drift on what
“assigned to this team” means — a team’s controls and a team’s evidence are selected by the same
rule.
Note the two endpoints paginate differently. The controls list is paginated — limit defaults to 50
and caps at 200 — and the filter is applied before the row count, so total reflects the filtered
set rather than the unfiltered one. The evidence list is not paginated and returns every row for
the organisation. It takes the same filters anyway: partly so the two lists agree, and partly because
an unpaginated list is the one you least want to send to a browser whole only to discard most of it.
A team filter on the controls list returns only controls that have been scoped. The list is catalogue-driven, and a control the organisation has never scoped has no row for an assignment to attach to.
The application’s own list views use these parameters, so the controls list filters across the whole catalogue rather than only the rows it has loaded.
Assignment changes are written to the audit log with entity_type of control_team_assignment or
evidence_team_assignment and an action of create, update or delete, tracking
scoped_control_id (or evidence_tracking_id), team_id and is_accountable. A promotion writes
two entries — the promotion and the demotion it caused. A re-post that changes nothing writes none,
so an idempotent client does not fill the trail with noise.
See User Management for the full guide.
Database Backup
Section titled “Database Backup”In the web interface, under Settings, Backups (Tenant Export / Import):
- Download Tenant Export — one organisation’s working data as a JSON file. A partial tenant export, not a disaster-recovery backup
- Import Tenant Data — upload a previous export, review the preview, then Confirm Restore
For a real backup use scripts/backup.sh — see
Backup & Restore
Evidence Storage
Section titled “Evidence Storage”Where an organisation’s evidence files are written. Each organisation may configure its own object store; one that has not falls back to the platform store, and then to whatever the process environment names, so an existing installation keeps working untouched. Organisation administrators drive this from Settings, then Evidence storage — see Evidence Storage Settings.
A configuration is created as a draft, which nothing resolves and nothing writes to. Activating it runs a real write, read-back and delete against the store first and refuses on any failure, so no configuration can go live without having been proved reachable.
The connection test. POST .../evidence-storage/test runs that same round trip on demand, against
a draft, an active row, or — with no config_id in the body — whatever is in force for the
organisation. It answers 200 with success: false when the store cannot be reached, rather than
an error status: an unreachable customer bucket is a result the administrator asked for, not a fault
in this API. Four steps are reported in order, each ok true or false:
| Step | What it did |
|---|---|
address | Resolved the configuration and built a client. Fails on an undecryptable secret, before any network call |
put | Wrote one throwaway object |
get | Read it back |
delete | Removed it |
A step that was never reached is reported with error_class: "NotAttempted" rather than omitted, so
the list always has four entries and the first ok: false is the failure. A step carries the store’s
status_code where there was one and an exception class name where there was not. Neither the
object’s content nor anything the store sent back is returned, and no credential appears in any
field.
| State | What it means |
|---|---|
draft | Editable, inert. Nothing resolves it |
active | Where this organisation’s evidence is written. One per organisation |
retired | Out of service, kept so evidence written under it is still readable |
| Provider | Endpoint | Notes |
|---|---|---|
| Amazon S3 | Derived from the region | The only provider that can use an ambient instance role instead of a key pair |
| Google Cloud Storage | Fixed, not editable | Reached over the S3-compatible XML API with an HMAC key pair |
| MinIO | Yours | Path-style addressing; no server-side encryption. Your own MinIO deployment — the bundled one is end-of-life, see the troubleshooting guide |
| S3-compatible | Yours | Any other S3 API |
Every endpoint typed into this screen is https only, whatever the provider — MinIO included — and
no endpoint resolving to a loopback, private, carrier-grade NAT or link-local address is accepted. The
http scheme and an internal address are permitted only for the bundled store the installer
provisions, which no request can create.
On /health
Section titled “On /health”GET /health carries an evidence_storage component. It takes no authentication, and it reports the
platform-wide effective store — the one an organisation with no configuration of its own falls back
to. It says nothing about any individual organisation’s store.
"evidence_storage": { "status": "ok", "source": "bundled", "provider": "minio" }| Field | Values |
|---|---|
status | ok, unconfigured, error. Only error degrades the overall status; unconfigured is a working --no-minio install and does not |
source | bundled, platform, legacy_env, or none |
provider | aws_s3, gcs, minio, s3_compatible, or null |
An error adds a fourth field carrying the exception class name only. No bucket, endpoint,
credential or hostname appears in any state: the endpoint is unauthenticated and a bucket name is an
asset inventory.
It never dials the store. The answer comes from the resolver’s cached snapshot, so a Docker healthcheck every 30 seconds costs the customer’s object store nothing and a network blip cannot flap container health. For a real reachability answer use the connection test above, which dials on demand and has an administrator to report the failure to. See Monitoring for the full table.
API Endpoints
Section titled “API Endpoints”All require the admin role in the organisation named in the path. A configuration id belonging to
another organisation answers 404, never 403.
POST /api/organizations/{org_id}/evidence-storage/test write, read back, delete; reports each stepPOST /api/organizations/{org_id}/evidence-storage creates a draftGET /api/organizations/{org_id}/evidence-storage this organisation's configurationsGET /api/organizations/{org_id}/evidence-storage/effective the one actually in force, and where it came fromGET /api/organizations/{org_id}/evidence-storage/{config_id}PATCH /api/organizations/{org_id}/evidence-storage/{config_id} drafts only; 409 otherwisePOST /api/organizations/{org_id}/evidence-storage/{config_id}/activate probes first, then goes livePOST /api/organizations/{org_id}/evidence-storage/{config_id}/rotate replaces the stored secretPOST /api/organizations/{org_id}/evidence-storage/{config_id}/retire out of service, row keptDELETE /api/organizations/{org_id}/evidence-storage/{config_id} 409 while active or referencedPOST /api/organizations/{org_id}/evidence-storage/{config_id}/copy-to/{target_config_id} 202 with the queued run; 422 same config or target not active; 409 if one is already runningGET /api/organizations/{org_id}/evidence-storage/copy-sources stores holding this org's evidence, minus the active one; includes the platform store when this organisation's files are stamped to itGET /api/organizations/{org_id}/evidence-storage/copy-runs recent copy runs, newest firstGET /api/organizations/{org_id}/evidence-storage/copy-runs/{run_id} one run's progressThe copy reads every evidence file of this organisation recorded under the source configuration, writes it to the target, verifies it by size and SHA-256 read-back, and re-points the row — one file per transaction, so a re-trigger resumes rather than repeats. It never deletes from the source.
The {config_id} in the copy path may be the platform configuration as a source, which is how a
bundled installation’s evidence gets out: activating an organisation’s first own store stamps its
existing files with the platform row, because that is where the bytes are. A platform id the
organisation neither resolves to nor has files stamped to answers 404, like any row that is not
theirs. The target is always strictly organisation-scoped, so no copy can write into the shared
store.
A source of this organisation’s own is retired only when no evidence file references it, which is also
the condition a DELETE checks, so a copy that left rows behind leaves the source both un-retired and
undeletable. A source that is the platform store, an is_bundled row, or another organisation’s row is
never retired: the run reports source_retired: false with a source_retired_reason naming why,
and the screen repeats that string verbatim. Run progress is held in Redis for a day, not in the
database: losing it loses the report, never the evidence.
POST .../activate also answers 409 with an unstamped_files count when the store in force is the
one named in the installation’s environment (legacy_env) and the organisation still has evidence
files not associated with any configuration row. There is no correct row to associate them with, and
activating anyway would leave them resolving to the new store, which does not hold them. Seed the
platform configuration — the installer does this — and retry.
The secret is encrypted with SCF_SECRET_KEY before it reaches the database and is never
returned: a configured row renders a fixed mask, the same eight characters whatever the secret is.
With no SCF_SECRET_KEY configured, the three writes that store a secret — create, edit and
rotate — are refused with a 409 naming that as the cause. Activate, retire and delete store no
secret and are unaffected.
Every configuration also reports where it came from and whether the operator manages it, in the
same two fields the Integrations screen uses: source and
managed_by_operator. A row of the
organisation’s own is org and the organisation’s admins may edit it; the platform row is platform
and belongs to whoever installed the platform. The effective read adds the third source, legacy_env
— the process environment, which is what an installation with no configurations at all resolves to,
and which no one can change from the application:
source | managed_by_operator | What it is |
|---|---|---|
org | false | This organisation’s own active configuration |
platform | true | The platform default, set by the installer or an operator |
legacy_env | true | No active configuration applies to this organisation, so the process environment is in force. config_id is null |
The effective read also carries is_bundled, which is true only for the object store this
installer provisioned into the stack. It is a column on the row, written once at seeding and by
nothing else — no request can set it. Read it rather than inferring “bundled” from
source == "platform" and a MinIO provider: an operator who configured a platform-wide MinIO of
their own matches that pattern and is not running the bundled store.
The effective read returns no credential — not the stored secret, not a mask of it, and not the access
key id. It also reports configured, which is false when nothing — no organisation row, no
platform row, and no usable environment setting — resolves for this organisation, and true once
something does. Read configured rather than the source to decide whether an object store is in
force: legacy_env is returned in both cases, and only configured separates an installation
running on its environment settings from one with no evidence storage at all.
Rotating an active configuration probes the store with the new credential before anything is written, so a bad credential leaves the old one in place instead of taking evidence storage down. A successful rotation bumps the configuration’s key version, which every process uses as part of its storage client cache key, so background workers pick the new credential up within a couple of seconds without a restart.
Install and Credentials
Section titled “Install and Credentials”Self-hosted only. The installer generates every credential; you type none on the bundled path. See Credentials and secrets.
scripts/install.sh --up # first run: wizard on 127.0.0.1:8765, then start the stackscripts/install.sh --unattended ./install.json # scripted / CI install: same validation, no browserscripts/install.sh --import-env # move an existing .env's credentials into 0600 filesscripts/install.sh --port 9765 # wizard on another loopback portscripts/install.sh --no-minio # recommended for real evidence: bundle NO object store, configure your own in the appssh -L 8765:127.0.0.1:8765 <user>@<host> # remote host: tunnel first, then open http://127.0.0.1:8765/| Item | Where |
|---|---|
| Credential files | $SCF_SECRETS_DIR (recorded in .env as an absolute path): a 0700 directory, one 0600 file per credential |
SCF_SECRET_KEY | $SCF_SECRETS_DIR/SCF_SECRET_KEY on an installer-provisioned install; an SCF_SECRET_KEY= line in .env on a legacy install. scripts/upgrade.sh generates it if absent and never overwrites it |
| Non-secret settings | .env in the checkout, including SCF_SECRETS_DIR and COMPOSE_FILE=docker-compose.yml:docker-compose.secrets.yml |
| Provisioning sentinel | $SCF_SECRETS_DIR/.provisioned — makes provisioning once-only; do not delete it |
| Provisioning token | $SCF_SECRETS_DIR/.provision-token — deleted when the wizard finishes; never in a URL, never in a backup |
Credential commands
Section titled “Credential commands”docker compose exec backend python -m cli.admin secrets-status # integration credential health; never prints a valuedocker compose exec backend python -m cli.admin backfill-encrypt # encrypt legacy plaintext rows in place (idempotent)docker compose exec backend python -m cli.admin rotate-secret-key # re-encrypt every stored credential under the primary SCF_SECRET_KEYHost backup set
Section titled “Host backup set”scripts/backup.sh # one backup set into ./backups, then prunescripts/upgrade.sh --rollback <TS> # restore a set, credential tarball includedscripts/upgrade.sh --resume-post-checkout <TS> # re-run only the second half of an upgrade (rebuild, migrate, start, verify, env fixups) against the code already checked out, with <TS> as its rollback point| File | Contents |
|---|---|
<TS>_v<version>.dump | Postgres custom-format dump (whole database) |
<TS>_v<version>_minio.tgz | MinIO evidence volume tarball |
secrets-<TS>.tar.gz | Credential files from SCF_SECRETS_DIR, mode 0600, excluding .provision-token. Not written on a legacy .env install — back up .env separately |
<TS>_ref.txt | Git ref at backup time |
<TS>_checksums.sha256 | Checksums of the data files |
Common Workflows
Section titled “Common Workflows”Starting a New Framework
Section titled “Starting a New Framework”- Go to Control Scoping
- Click Scope by Framework
- Select your target framework(s)
- Click Add to Scope
- Review and configure controls
Preparing for Audit
Section titled “Preparing for Audit”- Check Dashboard for overall status
- Review the Evidence Dashboard tab for gaps
- Filter by framework being audited
- Create tasks for missing evidence
- Verify all controls show green status
Daily Review
Section titled “Daily Review”- Check Dashboard for at-risk items
- Review Tasks for due/overdue items
- Check notification bell for mentions
- Update task status as needed
Related Guides
Section titled “Related Guides”- Getting Started — First steps
- Control Management — Working with controls
- Evidence Management — Managing evidence
- Troubleshooting — Common issues

