UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

88 lines • 4.65 kB
/** * localPassword — the `local-password` strategy: a password list the server * holds, for development, tests and a demo box. Refused in production by * `identityFromConfig` (Q3): a list in an environment variable is not a * production identity store. * * Pattern: a {@link PasswordChecker} behind the sign-in door. The door owns * everything a password door must do around the check (the guard, * attempt limits, one answer, a minimum response time); this file only * answers "is this the password for that name?". * * ── Hashed passwords only, with scrypt ────────────────────────────────────── * A list entry is `name:scrypt$<log2 N>$<r>$<p>$<salt>$<key>` — never a plain * password. Make one with {@link hashPassword}. * * Why scrypt: it is in `node:crypto`, so it adds no dependency (argon2 needs a * native module; bcrypt a package), and it is memory-hard, which PBKDF2 is not. * The default cost is OWASP's recommended scrypt setting (N = 2^17, r = 8, * p = 1: 128 MiB and a noticeable fraction of a second per check — on purpose). * The cost travels in the hash. Anything weaker than N = 2^14, r = 8, p = 1 is * refused, and so is anything dearer than 256 MiB per check (128·N·r) or * p > 4 — a check is run for every unknown name too, so an absurd cost is a * denial of service on the door. * * ── One cost per list ─────────────────────────────────────────────────────── * Every entry must carry the SAME cost. An unknown name is checked against a * decoy hash so it takes as long as a known one; with mixed costs no single * decoy can match every entry, and the time would tell which names exist. A * cost change (a re-hash) is therefore done for the whole list at once: this is * a development/test list the operator regenerates, not a live store that has * to migrate one person at a time as they sign in. * * ── Ids ───────────────────────────────────────────────────────────────────── * The one exception to "never a name a person types" (rule 7): a local * password's id IS its configured name, matched exactly (the door trims the * typed name once, as the app's own door did) — so one person cannot get two * owners by typing `Alice` and `alice`. Renaming someone orphans their * conversations. */ import type { PasswordChecker } from '../../hosting/signin/types.js'; /** The scrypt cost this library writes. OWASP Password Storage Cheat Sheet: N=2^17, r=8, p=1. */ export declare const SCRYPT_DEFAULT: { readonly log2N: 17; readonly r: 8; readonly p: 1; }; /** The weakest cost a stored hash may carry. */ export declare const SCRYPT_FLOOR: { readonly log2N: 14; readonly r: 8; readonly p: 1; }; /** The most memory one check may use: 128·N·r bytes. */ export declare const SCRYPT_MAX_BYTES: number; /** The most parallel passes one check may run. */ export declare const SCRYPT_MAX_P = 4; /** The cost of a hash, for {@link hashPassword}. */ export interface ScryptCost { readonly log2N: number; readonly r: number; readonly p: number; } /** A local-password list entry is refused. */ export declare class LocalPasswordConfigError extends Error { readonly code: "ERR_LOCAL_PASSWORD_CONFIG"; constructor(sentence: string); } /** * Hash a password for a `local-password` list entry. * * @example * // Read the password from stdin, so it never lands in shell history: * // read -rs PW && PW="$PW" node -e \ * // "import('agentfootprint/security').then(m => m.hashPassword(process.env.PW)).then(console.log)" * // Then SINGLE-QUOTE the list — a shell or a compose file expands `$17`: * // IDENTITY_LOCAL_USERS='priya:scrypt$17$8$1$…$…' */ export declare function hashPassword(password: string, cost?: ScryptCost): Promise<string>; /** * The `local-password` checker over a list: `name:hash` entries, comma * separated (the `IDENTITY_LOCAL_USERS` form), or a `{ name: hash }` object. * Refused at construction: an empty list, a plain password, a malformed or * too-weak hash, a name with `:` `,` or a control character, a duplicate name. */ export declare function localPasswords(users: string | Readonly<Record<string, string>>): PasswordChecker & { readonly names: readonly string[]; }; //# sourceMappingURL=localPassword.d.ts.map