{"record":{"id":"d1a409fcfbdef84a","repo":"can1357/oh-my-pi","slug":"security-disabled","errorCode":null,"errorMessage":"Security disabled","messagePattern":"Security disabled","errorType":"exception","errorClass":"SecurityDisabledError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/security-protocol.ts","lineNumber":120,"sourceCode":"\treadonly scheme = \"security\";\n\treadonly immutable = true;\n\treadonly #resolveStore: SecurityStoreResolver;\n\treadonly #enabled: () => boolean;\n\n\tconstructor(\n\t\tresolveStore: SecurityStoreResolver = (cwd, signal) => SecurityStore.openForCwd(cwd, { signal }),\n\t\tenabled: () => boolean = isSecurityEnabled,\n\t) {\n\t\tthis.#resolveStore = resolveStore;\n\t\tthis.#enabled = enabled;\n\t}\n\n\tasync #store(context?: ResolveContext): Promise<SecurityStore> {\n\t\treturn this.#resolveStore(path.resolve(context?.cwd ?? process.cwd()), context?.signal);\n\t}\n\n\tasync resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {\n\t\tif (!(securityEnabledFromContext(context) ?? this.#enabled())) throw new SecurityDisabledError();\n\t\tconst parts = splitSecurityPath(url);\n\t\tconst store = await this.#store(context);\n\t\tif (parts.length === 0) {\n\t\t\treturn createSecurityResource({\n\t\t\t\turl: \"security://\",\n\t\t\t\tcontent: [\n\t\t\t\t\t\"# Security\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"OMP-owned software-security analysis resources. The namespace is read-only; use explicit security commands or tools for mutations.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"- `security://scans` — list scans\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\tcontentType: \"text/markdown\",\n\t\t\t\tisDirectory: true,\n\t\t\t});\n\t\t}\n\t\tif (parts[0] !== \"scans\") throw new Error(`Unknown security resource: security://${parts.join(\"/\")}`);","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/security-protocol.ts#L102-L138","documentation":"The security:// protocol handler is gated behind the security.enabled setting. At the top of resolve(), if neither the ResolveContext settings nor the global settings report security.enabled === true, the handler throws SecurityDisabledError with the message \"Security disabled\" (expanded text tells the user to set security.enabled = true). This protects the read-only security scan namespace so it is only reachable when the user has opted in.","triggerScenarios":"Calling SecurityProtocolHandler.resolve(url) (e.g. resolving security:// or security://scans/...) when (a) no context is passed and the global security.enabled setting is false (or unset and defaults to false), or (b) context.settings.get(\"security.enabled\") returns false or a non-boolean, overriding to disabled.","commonSituations":"An agent or script tries to read security://scans on a fresh install where the feature was never enabled; a caller passes a ResolveContext whose settings object lacks a boolean for security.enabled; tests or embeddings construct the handler with the default isSecurityEnabled but never toggle the setting; a settings schema change leaves the default off.","solutions":["Enable the feature: set security.enabled = true in settings (Settings → Tools → Security) or via the settings API before resolving.","If resolving programmatically, pass a ResolveContext whose settings.get(\"security.enabled\") returns true.","Check the return of isSecurityEnabled() before attempting security:// lookups and surface the enable-instructions message to the user instead of failing.","If you expected it to be enabled, verify the settings file actually loaded (isSettingsInitialized) and that no project config overrides security.enabled to false."],"exampleFix":"// before\nconst res = await handler.resolve(new URL(\"security://scans\"));\n// after\nif (!isSecurityEnabled()) {\n  throw new Error(\"Enable security: set security.enabled = true in settings.\");\n}\nconst res = await handler.resolve(new URL(\"security://scans\"));","handlingStrategy":"type-guard","validationCode":"import { isSecurityEnabled } from \"./internal-urls/security-protocol\";\n\nasync function resolveSecurityUrl(url: URL, context?: ResolveContext) {\n  if (!isSecurityEnabled()) {\n    throw new Error(\"security:// is disabled. Set security.enabled = true (Settings → Tools → Security).\");\n  }\n  return securityHandler.resolve(url as InternalUrl, context);\n}","typeGuard":"function securityEnabledIn(context?: ResolveContext): boolean {\n  if (!context?.settings || typeof context.settings !== \"object\") return isSecurityEnabled();\n  try {\n    const get = Reflect.get(context.settings, \"get\");\n    if (typeof get !== \"function\") return isSecurityEnabled();\n    const enabled = Reflect.apply(get, context.settings, [\"security.enabled\"]);\n    return typeof enabled === \"boolean\" ? enabled : isSecurityEnabled();\n  } catch {\n    return isSecurityEnabled();\n  }\n}","tryCatchPattern":"try {\n  return await handler.resolve(url, ctx);\n} catch (err) {\n  if (err instanceof SecurityDisabledError) {\n    logger.warn(\"security:// access attempted while disabled\", { url: url.href });\n    return null; // or surface err.message to the user as guidance\n  }\n  throw err;\n}","preventionTips":["Check isSecurityEnabled() (or your context settings) before building any security:// URL.","Enable security.enabled in project settings for projects that use security scans.","When passing ResolveContext, ensure settings.get(\"security.enabled\") returns a boolean.","Catch SecurityDisabledError by instanceof — it is an exported class, not a generic Error."],"tags":["internal-urls","feature-disabled","settings"],"backgroundTag":"feature-disabled","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}