Files
Open_Ultra_SAST/docs/rustfs.md
T
Marius CiepluchandClaude Opus 5.5 95a252b89b docs: MkDocs site with flow diagrams; RustFS permissions
Material for MkDocs site (mkdocs.yml, optional extra `docs`) with Mermaid via
the superfences custom fence. New pages: overview, scanning (modes, quick
rules, Joern engine, priors, pre-push delta), the plane on ax (one task as a
sequence, golden snapshot, Run DAGs), memory (row kinds, write/read paths,
FileStore vs S3, verify at startup), memory and detection, and RustFS setup
(admin steps, complete agent policy, .env, refusal messages, Select limit).
The ops READMEs are included at build time; evaluation gains the population
lifecycle and the fold layout. tests/test_docs_site.py runs the strict build
when mkdocs is importable and checks Mermaid blocks and the RustFS page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
2026-10-02 08:17:40 +02:00

9.8 KiB

RustFS setup for the memory store

The plane's memory store can live in an S3-compatible bucket: OUSAST_MEMORY=minio://sast-memory selects MinioStore (src/openultrasast/plane/memory.py), which works with RustFS and MinIO. The maintainer's server is RustFS at files.because-security.com, bucket sast-memory, store at the bucket root. What the store keeps there is described on Memory.

The store never configures its bucket. An admin sets the bucket up once with admin credentials. The agent account the store runs as can read the bucket's settings but not change them, and reads and writes objects freely. Every time the store opens, it checks the setup and refuses to start if anything is missing.

flowchart LR
    admin["Admin, once: versioning on, runs/ expiry rule"] --> bucket[("Bucket sast-memory")]
    agent["Agent account: read settings, read and write objects and tags"] --> verify{"verify_bucket at startup"}
    bucket --> verify
    verify -- "all present" --> store["MinioStore serves reads and writes"]
    verify -- "anything missing" --> refuse["MemoryStoreError naming each missing piece and its admin command"]

One-time admin steps

Run these with admin credentials (not the agent's). Both use the AWS CLI against the RustFS endpoint.

1. Turn versioning on. Provenance cites object version ids.

aws s3api put-bucket-versioning \
    --endpoint-url https://files.because-security.com \
    --bucket sast-memory \
    --versioning-configuration Status=Enabled

2. Expire raw run outputs under runs/ after 30 days. Everything else (repos/, facts/, index.jsonl and the blobs) is kept, so the rule must be a plain prefix filter on runs/.

aws s3api put-bucket-lifecycle-configuration \
    --endpoint-url https://files.because-security.com \
    --bucket sast-memory \
    --lifecycle-configuration '{"Rules":[{"ID":"ousast-runs-expiry","Status":"Enabled","Filter":{"Prefix":"runs/"},"Expiration":{"Days":30}}]}'

put-bucket-lifecycle-configuration replaces every rule on the bucket. If the bucket already has rules, merge this one into them first. The rule id ousast-runs-expiry and the 30 days are the store's defaults (RUNS_RULE_ID, RUNS_EXPIRE_DAYS); the store accepts any enabled, untagged prefix rule on runs/ with a number of days.

To read the result back:

aws s3api get-bucket-versioning --endpoint-url https://files.because-security.com --bucket sast-memory
aws s3api get-bucket-lifecycle-configuration --endpoint-url https://files.because-security.com --bucket sast-memory

The agent policy

Attach this policy to the agent account (the account whose keys go into .env). It is the complete policy: nothing else is needed, and it grants no Put* on versioning or lifecycle.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "BucketSettingsReadOnly",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:ListBucketVersions",
        "s3:GetBucketLocation",
        "s3:GetBucketVersioning",
        "s3:GetLifecycleConfiguration"
      ],
      "Resource": ["arn:aws:s3:::sast-memory"]
    },
    {
      "Sid": "ObjectsReadWrite",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:DeleteObjectVersion",
        "s3:GetObjectTagging",
        "s3:PutObjectTagging",
        "s3:GetObjectVersionTagging"
      ],
      "Resource": ["arn:aws:s3:::sast-memory/*"]
    }
  ]
}
Statement Resource In one line
BucketSettingsReadOnly arn:aws:s3:::sast-memory The agent can list the bucket and its versions and read its location, versioning and lifecycle settings, which is what the startup check needs; it cannot change them.
ObjectsReadWrite arn:aws:s3:::sast-memory/* The agent reads, writes and deletes objects and their versions and reads and writes object tags; this covers row objects, blobs, the index, the Select probe, and the kind tag the store filters by.

So the agent writes objects freely but cannot change bucket settings: turning versioning off or removing the expiry rule needs the admin.

The .env settings

The store reads its endpoint and credentials from .env in the working directory or from the environment (minio_settings), never from a manifest, and never prints them.

Variable Value for this server Notes
OUSAST_MEMORY minio://sast-memory minio://<bucket>[/<prefix>]; unset means the local FileStore
MINIO_ENDPOINT files.because-security.com host[:port] without a scheme (the minio SDK's endpoint); required
MINIO_ACCESS_KEY the agent account's access key required
MINIO_SECRET_KEY the agent account's secret key required
MINIO_SECURE true TLS; 0, false, no or off turn it off; default true
MINIO_REGION optional spares a GetBucketLocation call
  • Never commit .env. It is gitignored (.gitignore: .env, .env.*, except .env.example). .env.example lists these names with empty values.
  • .env never overrides a variable already exported in your shell (config.load_dotenv). A stale export MINIO_SECRET_KEY=... in your profile silently wins over the file; unset it when a change in .env seems to have no effect.
  • The store needs the minio extra (uv sync --extra minio); without it opening the store fails with OUSAST_MEMORY=minio://... needs the minio SDK: install openultrasast[minio]. A missing variable fails with OUSAST_MEMORY=minio://... needs MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY in .env or the environment (naming only the missing ones).

What the store verifies at startup

MinioStore.verify_bucket() runs once per process for each bucket and prefix. It reads the versioning status and the lifecycle rules, writes a two-line probe object at _probe/select.jsonl (under the prefix, if any), queries it with S3 Select and reads its tags. Every missing piece is collected into one error:

memory minio://sast-memory: the bucket is not set up for the store. The store never configures its bucket (an admin sets it once, the store verifies); missing:
  - <one line per missing piece>

The lines, exactly as the code writes them for this bucket at the root (<failure> is the server's error code and the first 200 characters of its message):

Check Refusal line
versioning unreadable versioning cannot be read (<failure>): the agent needs s3:GetBucketVersioning on arn:aws:s3:::sast-memory, and versioning must be Enabled
versioning off versioning is Off, it must be Enabled (provenance cites object versions). Admin: aws s3api put-bucket-versioning --endpoint-url "$MINIO_ENDPOINT" --bucket sast-memory --versioning-configuration Status=Enabled (the actual status, e.g. Suspended, replaces Off when set)
lifecycle unreadable the lifecycle configuration cannot be read (<failure>): the agent needs s3:GetLifecycleConfiguration on arn:aws:s3:::sast-memory, and a rule must expire runs/
a rule expires everything lifecycle rule(s) <rule ids> expire the whole store (repos/, facts/ and the index would be deleted); limit them to runs/
no runs/ rule no enabled lifecycle rule expires runs/ after a number of days. Admin (merged with any rules the bucket already has): aws s3api put-bucket-lifecycle-configuration --endpoint-url "$MINIO_ENDPOINT" --bucket sast-memory --lifecycle-configuration '{"Rules":[{"ID":"ousast-runs-expiry","Status":"Enabled","Filter":{"Prefix":"runs/"},"Expiration":{"Days":30}}]}'
tags unreadable object tags cannot be read on _probe/select.jsonl (<failure>): the store filters row objects by their kind tag, so the agent needs s3:GetObjectTagging on arn:aws:s3:::sast-memory/*
S3 Select missing S3 Select (SelectObjectContent over JSON Lines) does not answer on _probe/select.jsonl (<failure>). The store requires it and has no local fallback: use a server with S3 Select (RustFS has it; some MinIO releases removed it) and let the agent s3:PutObject and s3:GetObject

The admin commands inside the messages use --endpoint-url "$MINIO_ENDPOINT"; with the host-only MINIO_ENDPOINT above, pass the full URL (https://files.because-security.com) as in the steps on this page.

There is no fallback: the store does not start in a degraded mode, does not drop to a local store, and does not fetch and filter objects itself.

The RustFS Select limit and the fixed-schema rule

RustFS's S3 Select infers an object's JSON schema from its leading rows. A where field that is absent from those rows is unbound, even if a later row has it: the query fails with EvaluatorBindingDoesNotExist instead of matching (measured 2026-10-02 with the field first present at row 5000). The store does not read that as "no rows", since that answer could silently drop matches; it raises:

memory minio://sast-memory: S3 Select on <key> failed (EvaluatorBindingDoesNotExist: ...) (a field of [<fields>] is absent from the object's leading rows, which the server reads as its schema); the store has no local fallback

The rule that follows: a field the store filters on must be present in every row of its kind from the first row on, i.e. each row kind has a fixed set of queryable fields. Enforcing that set per kind in the store is in progress and not on main as of this page (see Memory).

Checks against the real server

The contract tests that talk to a real server run only with OUSAST_MEMORY_TEST_MINIO=1 (bucket OUSAST_MEMORY_TEST_BUCKET, else MINIO_BUCKET). They verify the bucket at its root and write only under a fresh contract-<id>/ prefix; they never configure the bucket. More on the host setup: ax on this host.