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
TypeScript
/**
* 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