KeygraphHQ/shannon · error · PentestError

Login instructions template not found

Error message

Login instructions template not found

What it means

Thrown by buildLoginInstructions when the shared login template file at <promptsBaseDir>/shared/login-instructions.txt does not exist (fs.pathExists returns false). The template is required whenever config.authentication is set, because interpolateVariables calls buildLoginInstructions to fill the {{LOGIN_INSTRUCTIONS}} placeholder. Category 'filesystem', non-retryable.

Source

Thrown at apps/worker/src/services/prompt-manager.ts:163

}

interface IncludeReplacement {
  placeholder: string;
  content: string;
}

// Pure function: Build complete login instructions from config
async function buildLoginInstructions(
  authentication: Authentication,
  logger: ActivityLogger,
  promptsBaseDir: string = PROMPTS_DIR,
): Promise<string> {
  try {
    // 1. Load the login instructions template
    const loginInstructionsPath = path.join(promptsBaseDir, 'shared', 'login-instructions.txt');

    if (!(await fs.pathExists(loginInstructionsPath))) {
      throw new PentestError('Login instructions template not found', 'filesystem', false, { loginInstructionsPath });
    }

    const fullTemplate = await fs.readFile(loginInstructionsPath, 'utf8');

    const getSection = (content: string, sectionName: string): string => {
      const regex = new RegExp(`<!-- BEGIN:${sectionName} -->([\\s\\S]*?)<!-- END:${sectionName} -->`, 'g');
      const match = regex.exec(content);
      return match?.[1]?.trim() ?? '';
    };

    // 2. Extract sections based on login type
    const loginType = authentication.login_type?.toUpperCase();
    let loginInstructions = '';

    const commonSection = getSection(fullTemplate, 'COMMON');
    const authSection = loginType ? getSection(fullTemplate, loginType) : ''; // FORM or SSO
    const verificationSection = getSection(fullTemplate, 'VERIFICATION');

View on GitHub (pinned to 1ae0a142f8)

Solutions

  1. Verify the file exists in the running container: docker exec <worker> ls /app/apps/worker/prompts/shared/login-instructions.txt (npx) or check ./apps/worker/prompts/shared/login-instructions.txt (local).
  2. Restore or rebuild the prompts tree from the repo, then re-run.
  3. If using a promptDir override, confirm it resolves to a directory that still contains shared/login-instructions.txt.
  4. In local mode, ensure SHANNON_WORKER_ROOT is unset or correctly points at apps/worker so PROMPTS_DIR resolves to the full prompt set.

Example fix

// before: promptDir override omits shared/
//   loadPrompt('vuln-injection', vars, config, false, logger, './my-prompts')
//   -> buildLoginInstructions looks for ./my-prompts/shared/login-instructions.txt (missing)
// after: keep the standard prompts dir, or symlink the shared tree
//   ln -s ./apps/worker/prompts/shared ./my-prompts/shared
Defensive patterns

Strategy: validation

Validate before calling

// Before starting an authenticated scan, confirm the template is present and readable
import { pathExists } from 'fs-extra';
const templatePath = path.join(PROMPTS_DIR, 'shared', 'login-instructions.txt');
if (config.authentication && !(await pathExists(templatePath))) {
  throw new Error(`Missing required login template at ${templatePath}`);
}

Type guard

function hasAuthentication(auth: unknown): auth is Authentication {
  return typeof auth === 'object' && auth !== null &&
    ('login_type' in auth || 'login_flow' in auth || 'credentials' in auth);
}

Try / catch

try {
  await loadPrompt('vuln-auth', vars, config, false, logger);
} catch (e) {
  if (e instanceof PentestError && e.type === 'filesystem' && /Login instructions template not found/.test(e.message)) {
    // restore the prompts tree in the image, then retry; otherwise disable auth and re-run unauthenticated
  }
  throw e;
}

Prevention

When it happens

Trigger: A scan config declares an authentication block (config.authentication.login_flow or login_type) so interpolateVariables invokes buildLoginInstructions, but the prompts directory mounted into the worker container is missing shared/login-instructions.txt. Common in local mode when the prompts dir override (SHANNON_WORKER_ROOT or promptDir) points at a partial tree, or in a custom Docker image that did not copy apps/worker/prompts/.

Common situations: Running local mode with a promptDir override that omits the shared/ subfolder. A custom worker image that pruned prompt files. An npx image older than the code expecting a newer template. Mis-set SHANNON_WORKER_ROOT pointing at the repo root instead of the worker package.

Related errors


AI-assisted analysis of KeygraphHQ/shannon@1ae0a142f8 (2026-08-12). Data as JSON: /api/errors/7ff32edfcd0f0d60. Report an issue: GitHub.