Files
ECC/scripts/hooks/hook-input.js
T
He DongandGitHub 8bf16ccfec fix(hooks): keep silent hook paths silent (#2987)
* fix(hooks): keep silent hook paths silent

* fix(hooks): harden stream failure handling

* fix(hooks): settle interrupted input streams

* test(hooks): name stream input limits

* test(hooks): isolate PostToolUse dispatcher fixtures
2026-09-18 18:42:20 -04:00

70 lines
2.1 KiB
JavaScript

'use strict';
const { StringDecoder } = require('string_decoder');
const DEFAULT_MAX_STDIN = 1024 * 1024;
function resolveMaxStdin(value, options = {}) {
const writeDiagnostic = options.writeDiagnostic || (() => {});
if (value === undefined || value === '') return DEFAULT_MAX_STDIN;
const parsed = Number(value);
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
writeDiagnostic(
'[Hook] ECC_HOOK_INPUT_MAX_BYTES must be a positive safe integer; using the 1 MiB default\n'
);
return DEFAULT_MAX_STDIN;
}
if (parsed > DEFAULT_MAX_STDIN) {
writeDiagnostic(
'[Hook] ECC_HOOK_INPUT_MAX_BYTES exceeds the 1 MiB safety maximum; clamping to 1 MiB\n'
);
return DEFAULT_MAX_STDIN;
}
return parsed;
}
function readStdinRaw(stream = process.stdin, options = {}) {
const maxStdin = options.maxStdin || DEFAULT_MAX_STDIN;
const decoder = new StringDecoder('utf8');
let raw = '';
let acceptedBytes = 0;
let truncated = options.truncated === true;
return new Promise(resolve => {
let settled = false;
stream.on('data', chunk => {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
const remaining = Math.max(0, maxStdin - acceptedBytes);
const accepted = buffer.subarray(0, remaining);
if (accepted.length > 0) {
raw += decoder.write(accepted);
acceptedBytes += accepted.length;
}
if (accepted.length < buffer.length) truncated = true;
});
const finish = () => {
if (settled) return;
settled = true;
if (!truncated) raw += decoder.end();
resolve({ raw, truncated });
};
const finishIncomplete = () => {
if (settled) return;
truncated = true;
finish();
};
stream.once('end', finish);
// A transport error or premature close can leave a syntactically plausible
// prefix behind. Mark it incomplete so safety hooks remain fail closed.
stream.once('error', finishIncomplete);
stream.once('close', finishIncomplete);
});
}
module.exports = {
DEFAULT_MAX_STDIN,
readStdinRaw,
resolveMaxStdin
};