Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b70f81f
chore(release): bump version to v6.13.5.rc1
lstein Jun 30, 2026
fd58147
fix(qwen): estimate Qwen Image VAE working memory so the cache frees …
lstein Jul 1, 2026
f8e9018
docs: add 3d party GPU hosting services (#9299)
lstein Jul 4, 2026
89cf74d
chore(release): bump version to 6.13.5
lstein Jul 4, 2026
9a3bd1d
chore(backend): ruff
lstein Jul 4, 2026
b0e2da7
fix(qwen): remap legacy single-file VL encoder keys under transformer…
Pfannkuchensack Jul 5, 2026
0b8ddad
chore(release): bump version
lstein Jul 5, 2026
3283b1e
fix(api): require auth on model-manager and app-info read endpoints
lstein Jul 19, 2026
8b42fb4
chore(api): regenerate openapi.json for new auth dependencies
lstein Jul 19, 2026
c551f5f
fix(api): make runtime_config administrator-only
lstein Jul 19, 2026
f70267a
fix(api): address review — PATCH redaction, /missing access, cache UI…
lstein Jul 20, 2026
9cbc634
fix: address self-review findings — scan_folder crash, stale spec, UI…
lstein Jul 21, 2026
5dc1e4f
fix(tests): satisfy CI ruff 0.11.2 — drop zip() flagged by B905, appl…
lstein Jul 21, 2026
8af2779
fix(api): address review round 2 — auth on upload-entry stub, complet…
lstein Jul 21, 2026
f716b7e
fix(ui): clear API cache on logout
JPPhoto Jul 21, 2026
8d70708
chore(release): bump version to 6.13.7
lstein Jul 21, 2026
bb86edd
fix(download): remove the download_queue REST API; block non-public U…
lstein Aug 13, 2026
eb72163
chore(release): bump version to 6.13.8
lstein Aug 13, 2026
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
28 changes: 28 additions & 0 deletions docs/src/content/docs/features/Multi-User Mode/admin-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,34 @@ view the models previously installed, but cannot add or modify them.

## Security

### Download URL Restrictions

These restrictions apply to every download the server performs on your behalf —
model installs from URLs are the main case. (The old general-purpose
`/api/v1/download_queue` REST endpoint, which accepted an arbitrary URL and
destination path, has been removed.)

Downloads are refused when the URL resolves to a loopback, link-local or
private-network address. This stops a download URL from being used to reach
services that only the server can see, such as an admin interface bound to
`127.0.0.1` or a cloud instance-metadata endpoint. Redirects are checked at
every hop, so a public URL cannot bounce the server onto a private address.

If you install models from a mirror on your own network, re-enable those
addresses in `invokeai.yaml`:

```yaml
allow_private_download_urls: true
```

For security, the guarded downloader ignores ambient `HTTP_PROXY`, `HTTPS_PROXY`,
and `ALL_PROXY` settings. It still honors `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE`,
and `.netrc`. If direct outbound access is unavailable, set `download_proxy` to an
explicit proxy whose destination policy blocks private addresses; proxy-side DNS
cannot be checked by InvokeAI. Do not enable `allow_private_download_urls` merely to
make an untrusted proxy work. The two settings are independent: `download_proxy` is
honored whether or not `allow_private_download_urls` is enabled.

:::note[Strict Password Checking]
It is recommended that you enable strict password checking. This will
force all users to select good passwords that follow the
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ Ready to unleash your creativity? Invoke is available for Windows, macOS, and Li

---

:::note[About the Hosted Version]
The Invoke hosted platform has been shut down as the founding team joined Adobe. However, Invoke lives on as a thriving open-source project maintained by the community.
:::note[About the former officially-hosted version]
The Invoke.ai hosted platform has been shut down as the founding team joined Adobe. However, Invoke lives on as a thriving open-source project maintained by the community.

The open-source version offers the same powerful features you may have used in the hosted service, with the added benefit of complete control and privacy through self-hosting.

Expand Down
22 changes: 22 additions & 0 deletions docs/src/generated/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,28 @@
"type": "<class 'bool'>",
"validation": {}
},
{
"category": "MODEL INSTALL",
"default": false,
"description": "Allow the download queue to fetch from loopback, link-local and private-network addresses. Disabled by default so that a download URL cannot be used to reach services that are only reachable from the server. Enable this only if you install models from a mirror on your own network.",
"env_var": "INVOKEAI_ALLOW_PRIVATE_DOWNLOAD_URLS",
"literal_values": [],
"name": "allow_private_download_urls",
"required": false,
"type": "<class 'bool'>",
"validation": {}
},
{
"category": "MODEL INSTALL",
"default": null,
"description": "Optional HTTP proxy for model downloads. The proxy must enforce the public-address policy because proxy-side DNS cannot be checked by InvokeAI.",
"env_var": "INVOKEAI_DOWNLOAD_PROXY",
"literal_values": [],
"name": "download_proxy",
"required": false,
"type": "typing.Optional[str]",
"validation": {}
},
{
"category": "MODEL INSTALL",
"default": false,
Expand Down
51 changes: 50 additions & 1 deletion docs/src/lib/components/DownloadOptions.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { LinkCard, Icon, LinkButton } from '@astrojs/starlight/components';
import { CardGrid, LinkCard, Icon, LinkButton } from '@astrojs/starlight/components';
import { type StarlightIcon } from '@astrojs/starlight/types';
import { withBase } from '../base-path';

Expand Down Expand Up @@ -49,6 +49,24 @@ const manualDownloadOptions = {
href: withBase('/configuration/docker/', import.meta.env.BASE_URL),
},
};

const hostedOptions = {
aibadgr: {
headline: 'Run on AI Badgr',
description: 'Run on the AI Badgr hosted GPU service',
href: 'https://aibadgr.com/gpu/launch?template=invokeai',
},
runpod: {
headline: 'Run on RunPod',
description: 'Run on the RunPod hosted GPU service',
href: 'https://www.runpod.io/blog/invoke-ai-stable-diffusion-runpod-nfz18',
},
railway: {
headline: 'Run on Railway',
description: 'Run on the Railway hosted GPU service',
href: 'https://railway.com/deploy/invokeai',
},
};
---

<div class="download-options">
Expand Down Expand Up @@ -92,6 +110,20 @@ const manualDownloadOptions = {
))
}
</div>

<!-- Hosted Options -->
<div class="download-options__hosted">
<h3>Hosted Options</h3>
<p>For users who want to run Invoke on a hosted GPU service instead of their own hardware.</p>

<CardGrid>
{
Object.entries(hostedOptions).map(([key, { headline, href, description }]) => (
<LinkCard title={headline} {href} {description} />
))
}
</CardGrid>
</div>
</div>

<style is:global>
Expand Down Expand Up @@ -134,6 +166,23 @@ const manualDownloadOptions = {
}
}

.download-options__hosted {
margin: 0;
border-radius: var(--radius);
background: var(--sl-color-black);
border: 1px solid var(--sl-color-gray-5);
padding: 1.5rem;

h3 {
margin-top: 0;
}

> p {
margin-top: 0.5rem;
color: var(--sl-color-gray-2);
}
}

.download-options--launcher {
display: grid;
gap: 1.5rem;
Expand Down
5 changes: 5 additions & 0 deletions docs/src/styles/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,11 @@ article.card {

/* Splash Page-specific styles */

/* Make the default body font a bit smaller on the splash/home page. */
[data-has-hero] .sl-markdown-content {
font-size: 0.9rem;
}

@keyframes splash-animate {
0% { background-position: 0% 0%; }
50% { background-position: 0% 100%; }
Expand Down
61 changes: 48 additions & 13 deletions invokeai/app/api/routers/app_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from fastapi.routing import APIRouter
from pydantic import BaseModel, Field, model_validator

from invokeai.app.api.auth_dependencies import AdminUserOrDefault
from invokeai.app.api.auth_dependencies import AdminUserOrDefault, CurrentUserOrDefault
from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.services.config.config_default import (
EXTERNAL_PROVIDER_CONFIG_FIELDS,
Expand Down Expand Up @@ -55,7 +55,7 @@ async def get_version() -> AppVersion:


@app_router.get("/app_deps", operation_id="get_app_deps", status_code=200, response_model=dict[str, str])
async def get_app_deps() -> dict[str, str]:
async def get_app_deps(current_user: CurrentUserOrDefault) -> dict[str, str]:
deps: dict[str, str] = {dist.metadata["Name"]: dist.version for dist in distributions()}
try:
cuda = getattr(getattr(torch, "version", None), "cuda", None) or "N/A" # pyright: ignore[reportAttributeAccessIssue]
Expand All @@ -70,7 +70,7 @@ async def get_app_deps() -> dict[str, str]:


@app_router.get("/patchmatch_status", operation_id="get_patchmatch_status", status_code=200, response_model=bool)
async def get_patchmatch_status() -> bool:
async def get_patchmatch_status(current_user: CurrentUserOrDefault) -> bool:
return PatchMatch.patchmatch_available()


Expand Down Expand Up @@ -139,12 +139,46 @@ def validate_explicit_nulls(self) -> "UpdateAppGenerationSettingsRequest":
return self


REDACTED_SECRET = "**********"


def _redact_config_secrets(config: InvokeAIAppConfig) -> InvokeAIAppConfig:
"""Return a copy of the config with credential fields masked.

The runtime config carries provider API keys and model-download bearer tokens. The route is admin-only, but no
client - not even an admin's browser - has any use for the raw values; the UI only cares whether a credential is
configured.

NOTE: coverage is by convention, not automatic. Only `*_api_key` fields listed in
EXTERNAL_PROVIDER_CONFIG_FIELDS plus `remote_api_tokens` and credentials embedded in
`download_proxy` are masked. If you add a credential to InvokeAIAppConfig under any other
name, you must extend this function or it will be served verbatim.
"""
updates: dict[str, Any] = {}

for field_name in EXTERNAL_PROVIDER_CONFIG_FIELDS:
if not field_name.endswith("_api_key"):
continue
if getattr(config, field_name, None):
updates[field_name] = REDACTED_SECRET

if config.remote_api_tokens:
updates["remote_api_tokens"] = [
pair.model_copy(update={"token": REDACTED_SECRET}) for pair in config.remote_api_tokens
]

if config.download_proxy and "@" in config.download_proxy:
updates["download_proxy"] = REDACTED_SECRET

return config.model_copy(update=updates) if updates else config


@app_router.get(
"/runtime_config", operation_id="get_runtime_config", status_code=200, response_model=InvokeAIAppConfigWithSetFields
)
async def get_runtime_config() -> InvokeAIAppConfigWithSetFields:
async def get_runtime_config(current_admin: AdminUserOrDefault) -> InvokeAIAppConfigWithSetFields:
config = get_config()
return InvokeAIAppConfigWithSetFields(set_fields=config.model_fields_set, config=config)
return InvokeAIAppConfigWithSetFields(set_fields=config.model_fields_set, config=_redact_config_secrets(config))


@app_router.patch(
Expand All @@ -169,7 +203,7 @@ async def update_runtime_config(

persisted_config.update_config(update_dict)
persisted_config.write_file(config.config_file_path)
return InvokeAIAppConfigWithSetFields(set_fields=config.model_fields_set, config=config)
return InvokeAIAppConfigWithSetFields(set_fields=config.model_fields_set, config=_redact_config_secrets(config))


@app_router.get(
Expand All @@ -178,7 +212,7 @@ async def update_runtime_config(
status_code=200,
response_model=list[ExternalProviderStatusModel],
)
async def get_external_provider_statuses() -> list[ExternalProviderStatusModel]:
async def get_external_provider_statuses(current_user: CurrentUserOrDefault) -> list[ExternalProviderStatusModel]:
statuses = ApiDependencies.invoker.services.external_generation.get_provider_statuses()
return [status_to_model(status) for status in statuses.values()]

Expand All @@ -189,7 +223,7 @@ async def get_external_provider_statuses() -> list[ExternalProviderStatusModel]:
status_code=200,
response_model=list[ExternalProviderConfigModel],
)
async def get_external_provider_configs() -> list[ExternalProviderConfigModel]:
async def get_external_provider_configs(current_admin: AdminUserOrDefault) -> list[ExternalProviderConfigModel]:
config = get_config()
return [_build_external_provider_config(provider_id, config) for provider_id in EXTERNAL_PROVIDER_FIELDS]

Expand Down Expand Up @@ -340,7 +374,7 @@ def _remove_external_models_for_provider(provider_id: str) -> None:
responses={200: {"description": "The operation was successful"}},
response_model=LogLevel,
)
async def get_log_level() -> LogLevel:
async def get_log_level(current_admin: AdminUserOrDefault) -> LogLevel:
"""Returns the log level"""
return LogLevel(ApiDependencies.invoker.services.logger.level)

Expand All @@ -352,6 +386,7 @@ async def get_log_level() -> LogLevel:
response_model=LogLevel,
)
async def set_log_level(
current_admin: AdminUserOrDefault,
level: LogLevel = Body(description="New log verbosity level"),
) -> LogLevel:
"""Sets the log verbosity level"""
Expand All @@ -364,7 +399,7 @@ async def set_log_level(
operation_id="clear_invocation_cache",
responses={200: {"description": "The operation was successful"}},
)
async def clear_invocation_cache() -> None:
async def clear_invocation_cache(current_admin: AdminUserOrDefault) -> None:
"""Clears the invocation cache"""
ApiDependencies.invoker.services.invocation_cache.clear()

Expand All @@ -374,7 +409,7 @@ async def clear_invocation_cache() -> None:
operation_id="enable_invocation_cache",
responses={200: {"description": "The operation was successful"}},
)
async def enable_invocation_cache() -> None:
async def enable_invocation_cache(current_admin: AdminUserOrDefault) -> None:
"""Clears the invocation cache"""
ApiDependencies.invoker.services.invocation_cache.enable()

Expand All @@ -384,7 +419,7 @@ async def enable_invocation_cache() -> None:
operation_id="disable_invocation_cache",
responses={200: {"description": "The operation was successful"}},
)
async def disable_invocation_cache() -> None:
async def disable_invocation_cache(current_admin: AdminUserOrDefault) -> None:
"""Clears the invocation cache"""
ApiDependencies.invoker.services.invocation_cache.disable()

Expand All @@ -394,6 +429,6 @@ async def disable_invocation_cache() -> None:
operation_id="get_invocation_cache_status",
responses={200: {"model": InvocationCacheStatus}},
)
async def get_invocation_cache_status() -> InvocationCacheStatus:
async def get_invocation_cache_status(current_admin: AdminUserOrDefault) -> InvocationCacheStatus:
"""Clears the invocation cache"""
return ApiDependencies.invoker.services.invocation_cache.get_status()
Loading
Loading