{"record":{"id":"b1a7e441c1669040","repo":"conductor-oss/conductor","slug":"env-backed-secrets-are-read-only","errorCode":null,"errorMessage":"env-backed secrets are read-only","messagePattern":"env-backed secrets are read-only","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"warning","filePath":"core/src/main/java/com/netflix/conductor/core/secrets/EnvVariableSecretsDAO.java","lineNumber":71,"sourceCode":"\n    @Override\n    public String getSecret(String name) {\n        return EnvVarLookup.lookup(prefix, name);\n    }\n\n    @Override\n    public boolean secretExists(String name) {\n        return getSecret(name) != null;\n    }\n\n    @Override\n    public List<String> listSecretNames() {\n        return new ArrayList<>(EnvVarLookup.allWithPrefix(prefix).keySet());\n    }\n\n    @Override\n    public void putSecret(String name, String value) {\n        throw new UnsupportedOperationException(\"env-backed secrets are read-only\");\n    }\n\n    @Override\n    public void deleteSecret(String name) {\n        throw new UnsupportedOperationException(\"env-backed secrets are read-only\");\n    }\n\n    @Override\n    public List<CredentialMeta> listWithMeta() {\n        List<CredentialMeta> result = new ArrayList<>();\n        EnvVarLookup.allWithPrefix(prefix)\n                .forEach((name, value) -> result.add(toMeta(name, value)));\n        for (String llmKey : KNOWN_LLM_API_KEYS) {\n            String value = EnvVarLookup.lookup(\"\", llmKey);\n            if (value != null) {\n                result.add(toMeta(llmKey, value));\n            }\n        }","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/core/src/main/java/com/netflix/conductor/core/secrets/EnvVariableSecretsDAO.java#L53-L89","documentation":"Thrown by EnvVariableSecretsDAO.putSecret when an attempt is made to write/create a secret via the env-backed secrets DAO. The env-backed DAO reads secrets exclusively from environment variables (with a configurable prefix like CONDUCTOR_SECRET_) and does not support write operations. This is an UnsupportedOperationException — a permanent limitation of the storage backend, not a transient failure.","triggerScenarios":"Calling the secrets API endpoint (POST /api/secrets/{name}) or any code path that invokes SecretsDAO.putSecret while the 'conductor.secrets.type' property is set to 'env' (which is the default). The env DAO is selected by @ConditionalOnProperty and is active by default.","commonSituations":"Application tries to write a secret through the API without configuring a writable secrets backend (e.g. database-backed or vault-backed). Default configuration uses env-backed secrets, which is read-only. Developer assumes the secrets API is fully functional for writes but hasn't changed conductor.secrets.type.","solutions":["Set environment variables with the configured prefix (default CONDUCTOR_SECRET_) to provide secrets instead of writing through the API.","Switch to a writable secrets backend by setting conductor.secrets.type to a supported writable implementation (e.g. database-backed DAO).","If you need both env-sourced and API-managed secrets, configure a secrets backend that supports writes.","Guard UI/API code paths that call putSecret with a capability check before invoking the write."],"exampleFix":"# before — trying to write a secret via API with env backend (default)\ncurl -X POST http://localhost:8080/api/secrets/MY_KEY -d 'value'\n\n# after — set the env var directly\nexport CONDUCTOR_SECRET_MY_KEY=value\n# or switch backend in application.properties:\n# conductor.secrets.type=database","handlingStrategy":"type-guard","validationCode":"// Check if the secrets backend supports writes before calling putSecret\nif (secretsDAO instanceof EnvVariableSecretsDAO || secretsDAO instanceof NoopSecretsDAO) {\n    throw new IllegalStateException(\n        \"Cannot write secrets with \" + secretsDAO.getClass().getSimpleName()\n            + \" — configure a writable secrets backend\");\n}\nsecretsDAO.putSecret(name, value);","typeGuard":"// Check write capability by testing with a no-op safe probe\npublic boolean canWriteSecrets(SecretsDAO dao) {\n    try {\n        // Use a marker that won't harm a real backend\n        // For env/noop, this throws UnsupportedOperationException\n        return !(dao instanceof EnvVariableSecretsDAO)\n            && !(dao instanceof NoopSecretsDAO);\n    } catch (Exception e) {\n        return false;\n    }\n}","tryCatchPattern":"try {\n    secretsDAO.putSecret(name, value);\n} catch (UnsupportedOperationException e) {\n    // Backend is read-only — guide the user to configure a writable backend\n    throw new IllegalStateException(\n        \"Secrets backend is read-only. Set conductor.secrets.type to a writable backend.\", e);\n}","preventionTips":["Set conductor.secrets.type to a writable backend if API-based secret management is required.","Provide secrets via environment variables (CONDUCTOR_SECRET_*) when using the env backend.","Build a capability check into the UI/API layer to disable write buttons when the backend is read-only."],"tags":["secrets","env-backed","read-only","configuration"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}