# Security Model ## Security objectives 1. Keep the OSS bucket Private while permitting stable customer-facing UUID image links. 2. Keep all raw API keys, database credentials, OSS AccessKeys, object keys, and Presigned URL query parameters out of Git and logs. 3. Restrict retrieval to configured knowledge bases and image signing to active Adapter mappings. 4. Fail closed when validation, a database, WeKnora, OSS credential files, or signing is unavailable. ## Credential model Because the target ECS instances are not permitted to receive RAM roles, the Adapter uses a dedicated RAM user's static AK. Test and production use different identities and different Private OSS buckets. The AccessKey ID and secret are mounted as separate read-only files; they are never accepted as Compose environment values, build arguments, API fields, or command-line arguments. The production RAM user policy grants only `oss:GetObject`. Alibaba Cloud documents `oss:GetObject` as the permission required to download an object.[4] The supplied policy narrows this action to: ```text acs:oss:*:*:${OSS_BUCKET}/weknora/${WEKNORA_TENANT_ID}/* ``` It intentionally omits List, Put, Delete, ACL, bucket-management, RAM management, and KMS permissions. A higher-privilege test AK may be used only for the explicitly approved test window; it must never be copied to production and must be replaced by a dedicated read-only identity before production traffic. If the reviewed bucket uses SSE-KMS, do not add `kms:Decrypt` automatically; review a separately scoped KMS policy first. ## Private OSS and temporary access A V4 Presigned GET URL grants temporary access to one object without requiring credentials at the recipient.[2] V4 URLs expose `x-oss-expires`, use `OSS4-HMAC-SHA256`, and carry a signature in the query string.[2] Therefore: - the URL lifetime is fixed at 600 seconds; - the generated scheme and hostname are revalidated as the configured public regional OSS host; - the URL appears only in the 302 `Location` header; - the response is `no-store` and `no-referrer`; - the URL is never persisted or logged; - a stable `/i/{UUID}` remains reusable after the temporary URL expires. The bucket must remain Private. Anonymous direct OSS access must return denial during Slice 9 acceptance. ## Secret storage Raw secrets are mounted as read-only files owned by container UID 10001 with mode `0400`: - `/run/secrets/weknora_api_key` - `/run/secrets/asset_database_url` - `/run/secrets/weknora_readonly_database_url` - `/run/secrets/oss_access_key_id` - `/run/secrets/oss_access_key_secret` They must not appear in `.env`, Compose environment, Docker build args, image layers, source, fixtures, documentation examples, or shell command history. Dify stores only current/next SHA-256 hashes in non-secret runtime configuration. ## Input and SSRF controls The Adapter does not perform arbitrary HTTP/HTTPS downloads. Callers cannot provide bucket, endpoint, object key, expiry, or destination URL. Only canonical `resource://` handles are accepted for first resolution; fixed tenant, storage backend, provider, bucket, and object-key rules are checked before persistence or signing. This removes the general outbound URL-fetch primitive that would create SSRF risk. Outbound application destinations are limited to configured WeKnora, PostgreSQL, and the SDK-derived regional OSS endpoint. ## Authentication and authorization - `POST /retrieval` requires a Dify Bearer key matching current or next hash. - Requested knowledge bases must be allowlisted before search. - Every returned chunk must repeat the requested knowledge-base UUID. - `GET /i/{UUID}` is intentionally anonymous; possession of a valid UUID grants access. - Missing and disabled UUIDs have the same empty 404 behavior. - `GET /healthz` discloses only `{"status":"ok"}`. ## Logging and error redaction The application writes one JSON request summary with only: - request ID; - method; - matched route template; - status code; - duration in milliseconds; - fixed error code for failures. The following are prohibited from application and Nginx access logs: - Dify or WeKnora API Key; - `Authorization` header; - request query/body and complete knowledge content; - `resource://` locator; - `object_key`; - database DSN; - OSS AccessKey ID/secret; - Presigned URL or signature fields. Nginx access logging is disabled for this virtual host. Unexpected exceptions return a fixed 500 without traceback or exception text in Adapter request logs. ## Container and network controls - runtime UID/GID 10001; no root process; - read-only root filesystem and no-exec temporary filesystem; - all Linux capabilities dropped; - `no-new-privileges` enabled; - process, CPU, and memory limits set; - Adapter published only on loopback; - TLS 1.2/1.3 terminated by host Nginx; - route/method allowlists, request-size limit, and rate limits at Nginx; - Uvicorn raw access log disabled. ## Review checklist - [ ] Private bucket confirmed; anonymous object GET denied. - [ ] Dedicated test/production RAM users and AccessKeys are separate. - [ ] Production RAM policy has only `oss:GetObject` for the reviewed bucket/tenant prefix. - [ ] No other policy grants overlapping OSS write/list access. - [ ] Five Secret files are owned by UID 10001 and mode `0400`. - [ ] Adapter DB and WeKnora DB use different least-privilege accounts. - [ ] Nginx certificate paths and public hostname are reviewed. - [ ] Sanitized logs contain request IDs but none of the prohibited values. - [ ] Arbitrary URL, bucket, object-key, and endpoint override tests pass. - [ ] Slice 9 real test-environment E2E and audit report are accepted. ## Sources [2] https://alibabacloud.com/help/en/oss/developer-reference/python-download-using-a-presigned-url — Alibaba Cloud OSS Python presigned GET URL [4] https://www.alibabacloud.com/help/en/oss/developer-reference/get-object — Alibaba Cloud OSS GetObject permission