feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
+16
View File
@@ -0,0 +1,16 @@
export * from './secret-custody/crypto';
export * from './secret-custody/keyring';
export * from './secret-custody/keyMaterial';
export * from './secret-custody/service';
export type {
AppendLocalSecretEnvelopeCommand,
AppendLocalSecretEnvelopeResult,
LocalSecretEnvelope,
LocalSecretEnvelopeRepository,
LocalSecretEnvironmentProvider,
LocalSecretKeyMaterial,
LocalSecretKeyProvider,
LocalSecretReference,
PutEncryptedLocalSecretCommand,
PutEncryptedLocalSecretResult,
} from '@qinglong/runtime-core/local-secret';
@@ -0,0 +1,112 @@
import {
createCipheriv,
createDecipheriv,
randomBytes,
} from 'node:crypto';
import { TextDecoder } from 'node:util';
import {
LOCAL_SECRET_ALGORITHM,
LocalSecretUnavailableError,
assertLocalSecretPlaintext,
localSecretBinary,
localSecretEnvelopeAad,
normalizeLocalSecretEnvelope,
type LocalSecretEnvelope,
} from '@qinglong/runtime-core/local-secret';
const SECRET_KEY_BYTES = 32;
const SECRET_NONCE_BYTES = 12;
const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });
export type LocalSecretNonceFactory = () => Uint8Array;
function ownedSecretKey(key: Uint8Array): Buffer {
if (!(key instanceof Uint8Array) || key.byteLength !== SECRET_KEY_BYTES) {
throw new LocalSecretUnavailableError();
}
return Buffer.from(key);
}
export function encryptLocalSecretEnvelope(
metadata: Omit<LocalSecretEnvelope, 'nonce' | 'ciphertext' | 'authTag'>,
plaintext: string,
key: Uint8Array,
nonceFactory: LocalSecretNonceFactory = () => randomBytes(SECRET_NONCE_BYTES),
): LocalSecretEnvelope {
assertLocalSecretPlaintext(plaintext);
const ownedKey = ownedSecretKey(key);
const plaintextBuffer = Buffer.from(plaintext, 'utf8');
let nonce: Buffer | undefined;
try {
nonce = Buffer.from(nonceFactory());
if (nonce.length !== SECRET_NONCE_BYTES) {
throw new LocalSecretUnavailableError();
}
const cipher = createCipheriv(LOCAL_SECRET_ALGORITHM, ownedKey, nonce, {
authTagLength: 16,
});
const aad = localSecretEnvelopeAad(metadata);
try {
cipher.setAAD(aad);
} finally {
aad.fill(0);
}
const ciphertext = Buffer.concat([
cipher.update(plaintextBuffer),
cipher.final(),
]);
try {
return normalizeLocalSecretEnvelope({
...metadata,
nonce: nonce.toString('base64url'),
ciphertext: ciphertext.toString('base64url'),
authTag: cipher.getAuthTag().toString('base64url'),
});
} finally {
ciphertext.fill(0);
}
} catch (error) {
if (error instanceof LocalSecretUnavailableError) throw error;
throw new LocalSecretUnavailableError();
} finally {
ownedKey.fill(0);
plaintextBuffer.fill(0);
nonce?.fill(0);
}
}
export function decryptLocalSecretEnvelopeToBuffer(
envelope: LocalSecretEnvelope,
key: Uint8Array,
): Buffer {
const normalized = normalizeLocalSecretEnvelope(envelope);
const ownedKey = ownedSecretKey(key);
const nonce = localSecretBinary('nonce', normalized.nonce);
const ciphertext = localSecretBinary('ciphertext', normalized.ciphertext);
const authTag = localSecretBinary('authTag', normalized.authTag);
const aad = localSecretEnvelopeAad(normalized);
try {
const decipher = createDecipheriv(LOCAL_SECRET_ALGORITHM, ownedKey, nonce, {
authTagLength: 16,
});
decipher.setAAD(aad);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
} catch {
throw new LocalSecretUnavailableError();
} finally {
ownedKey.fill(0);
nonce.fill(0);
ciphertext.fill(0);
authTag.fill(0);
aad.fill(0);
}
}
export function decodeLocalSecretPlaintext(plaintext: Buffer): string {
try {
return UTF8_DECODER.decode(plaintext);
} catch {
throw new LocalSecretUnavailableError();
}
}
@@ -0,0 +1,46 @@
import { timingSafeEqual } from 'node:crypto';
import {
LocalSecretUnavailableError,
assertLocalSecretKeyId,
type LocalSecretEnvelope,
type LocalSecretKeyMaterial,
} from '@qinglong/runtime-core/local-secret';
import { decryptLocalSecretEnvelopeToBuffer } from './crypto';
export function ownedLocalSecretKeyMaterial(
material: LocalSecretKeyMaterial | null,
expectedKeyId?: string,
): { keyId: string; key: Buffer } {
if (!material || !(material.key instanceof Uint8Array)) {
throw new LocalSecretUnavailableError();
}
try {
assertLocalSecretKeyId(material.keyId);
if (
(expectedKeyId !== undefined && material.keyId !== expectedKeyId) ||
material.key.byteLength !== 32
) {
throw new LocalSecretUnavailableError();
}
return { keyId: material.keyId, key: Buffer.from(material.key) };
} catch {
throw new LocalSecretUnavailableError();
} finally {
material.key.fill(0);
}
}
export function localSecretPlaintextMatches(
envelope: LocalSecretEnvelope,
key: Uint8Array,
expected: string,
): boolean {
const actual = decryptLocalSecretEnvelopeToBuffer(envelope, key);
const wanted = Buffer.from(expected, 'utf8');
try {
return actual.length === wanted.length && timingSafeEqual(actual, wanted);
} finally {
actual.fill(0);
wanted.fill(0);
}
}
@@ -0,0 +1,362 @@
import { randomBytes, createHash } from 'node:crypto';
import { constants } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
LocalSecretUnavailableError,
assertLocalSecretKeyId,
type LocalSecretKeyMaterial,
type LocalSecretKeyProvider,
} from '@qinglong/runtime-core/local-secret';
const MAX_KEYRING_BYTES = 16 * 1024;
const MAX_KEY_COUNT = 16;
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
interface LocalSecretKeyringManifest {
readonly version: 1;
readonly activeKeyId: string;
readonly keys: Readonly<Record<string, string>>;
}
export interface LocalSecretKeyringSummary {
readonly version: 1;
readonly activeKeyId: string;
readonly keyIds: readonly string[];
readonly digest: string;
}
export class LocalSecretKeyringConflictError extends Error {
readonly code = 'LOCAL_SECRET_KEYRING_CONFLICT';
constructor() {
super('Local Secret keyring state changed');
this.name = 'LocalSecretKeyringConflictError';
}
}
function exactKeys(value: object, expected: readonly string[]): boolean {
const actual = Object.keys(value).sort();
const sortedExpected = [...expected].sort();
return (
actual.length === sortedExpected.length &&
actual.every((key, index) => key === sortedExpected[index])
);
}
function assertKeyringPath(filePath: unknown): asserts filePath is string {
if (
typeof filePath !== 'string' ||
!path.isAbsolute(filePath) ||
filePath.includes('\0') ||
Buffer.byteLength(filePath, 'utf8') > 4096
) {
throw new TypeError('Local Secret keyring path must be absolute');
}
}
function parseManifest(value: Buffer): LocalSecretKeyringManifest {
let parsed: unknown;
try {
parsed = JSON.parse(value.toString('utf8'));
} catch {
throw new LocalSecretUnavailableError();
}
if (
!parsed ||
typeof parsed !== 'object' ||
Array.isArray(parsed) ||
!exactKeys(parsed, ['activeKeyId', 'keys', 'version'])
) {
throw new LocalSecretUnavailableError();
}
const record = parsed as Record<string, unknown>;
if (
record.version !== 1 ||
!record.keys ||
typeof record.keys !== 'object' ||
Array.isArray(record.keys)
) {
throw new LocalSecretUnavailableError();
}
try {
assertLocalSecretKeyId(record.activeKeyId);
} catch {
throw new LocalSecretUnavailableError();
}
const entries = Object.entries(record.keys as Record<string, unknown>);
if (entries.length < 1 || entries.length > MAX_KEY_COUNT) {
throw new LocalSecretUnavailableError();
}
const keys: Record<string, string> = Object.create(null);
for (const [keyId, encoded] of entries) {
let decoded: Buffer | undefined;
try {
assertLocalSecretKeyId(keyId);
decoded =
typeof encoded === 'string'
? Buffer.from(encoded, 'base64url')
: Buffer.alloc(0);
if (
typeof encoded !== 'string' ||
!BASE64URL_PATTERN.test(encoded) ||
decoded.length !== 32 ||
decoded.toString('base64url') !== encoded
) {
throw new LocalSecretUnavailableError();
}
keys[keyId] = encoded;
} catch {
throw new LocalSecretUnavailableError();
} finally {
decoded?.fill(0);
}
}
if (!keys[record.activeKeyId]) {
throw new LocalSecretUnavailableError();
}
return Object.freeze({
version: 1,
activeKeyId: record.activeKeyId,
keys: Object.freeze(keys),
});
}
function canonicalManifest(manifest: LocalSecretKeyringManifest): Buffer {
const keys = Object.fromEntries(
Object.entries(manifest.keys).sort(([left], [right]) =>
left.localeCompare(right),
),
);
return Buffer.from(
`${JSON.stringify({
version: 1,
activeKeyId: manifest.activeKeyId,
keys,
})}\n`,
'utf8',
);
}
function summary(manifest: LocalSecretKeyringManifest): LocalSecretKeyringSummary {
const canonical = canonicalManifest(manifest);
try {
return Object.freeze({
version: 1,
activeKeyId: manifest.activeKeyId,
keyIds: Object.freeze(Object.keys(manifest.keys).sort()),
digest: createHash('sha256').update(canonical).digest('hex'),
});
} finally {
canonical.fill(0);
}
}
async function assertRealParent(filePath: string): Promise<void> {
const stat = await fs.lstat(path.dirname(filePath));
if (!stat.isDirectory() || stat.isSymbolicLink()) {
throw new LocalSecretUnavailableError();
}
}
async function readManifest(filePath: string): Promise<LocalSecretKeyringManifest> {
let file: fs.FileHandle | undefined;
let contents: Buffer | undefined;
try {
file = await fs.open(
filePath,
constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0),
);
const stat = await file.stat();
if (
!stat.isFile() ||
stat.isSymbolicLink() ||
(stat.mode & 0o077) !== 0 ||
stat.size < 1 ||
stat.size > MAX_KEYRING_BYTES
) {
throw new LocalSecretUnavailableError();
}
contents = await file.readFile();
if (contents.length !== stat.size) {
throw new LocalSecretUnavailableError();
}
return parseManifest(contents);
} catch {
throw new LocalSecretUnavailableError();
} finally {
contents?.fill(0);
await file?.close().catch(() => undefined);
}
}
async function syncDirectory(directory: string): Promise<void> {
const handle = await fs.open(directory, constants.O_RDONLY);
try {
await handle.sync();
} finally {
await handle.close();
}
}
function newKeyId(): string {
return `qlsk-${randomBytes(12).toString('base64url')}`;
}
async function writeTemporary(
filePath: string,
contents: Buffer,
): Promise<string> {
const temporary = `${filePath}.tmp-${randomBytes(12).toString('hex')}`;
const handle = await fs.open(
temporary,
constants.O_CREAT |
constants.O_EXCL |
constants.O_WRONLY |
(constants.O_NOFOLLOW ?? 0),
0o600,
);
try {
await handle.writeFile(contents);
await handle.sync();
} finally {
await handle.close();
}
return temporary;
}
export class LocalSecretKeyringFileProvider implements LocalSecretKeyProvider {
private readonly filePath: string;
constructor(filePath: string) {
assertKeyringPath(filePath);
this.filePath = path.resolve(filePath);
}
async active(): Promise<LocalSecretKeyMaterial> {
const manifest = await readManifest(this.filePath);
return this.material(manifest, manifest.activeKeyId)!;
}
async resolve(keyId: string): Promise<LocalSecretKeyMaterial | null> {
try {
assertLocalSecretKeyId(keyId);
} catch {
throw new LocalSecretUnavailableError();
}
return this.material(await readManifest(this.filePath), keyId);
}
async inspect(): Promise<LocalSecretKeyringSummary> {
return summary(await readManifest(this.filePath));
}
private material(
manifest: LocalSecretKeyringManifest,
keyId: string,
): LocalSecretKeyMaterial | null {
const encoded = manifest.keys[keyId];
return encoded
? Object.freeze({
keyId,
key: Uint8Array.from(Buffer.from(encoded, 'base64url')),
})
: null;
}
}
export async function provisionLocalSecretKeyring(
filePath: string,
): Promise<LocalSecretKeyringSummary> {
assertKeyringPath(filePath);
const resolved = path.resolve(filePath);
await assertRealParent(resolved);
const keyId = newKeyId();
const key = randomBytes(32);
const manifest: LocalSecretKeyringManifest = Object.freeze({
version: 1,
activeKeyId: keyId,
keys: Object.freeze({ [keyId]: key.toString('base64url') }),
});
const contents = canonicalManifest(manifest);
let temporary: string | undefined;
try {
temporary = await writeTemporary(resolved, contents);
await fs.link(temporary, resolved);
await fs.unlink(temporary);
temporary = undefined;
await syncDirectory(path.dirname(resolved));
return summary(manifest);
} catch {
throw new LocalSecretUnavailableError();
} finally {
key.fill(0);
contents.fill(0);
if (temporary) await fs.unlink(temporary).catch(() => undefined);
}
}
export async function rotateLocalSecretKeyring(options: {
readonly filePath: string;
readonly expectedActiveKeyId: string;
}): Promise<LocalSecretKeyringSummary> {
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError('Local Secret keyring rotation options are invalid');
}
assertKeyringPath(options.filePath);
try {
assertLocalSecretKeyId(options.expectedActiveKeyId);
} catch {
throw new TypeError('Local Secret expected active key is invalid');
}
const resolved = path.resolve(options.filePath);
await assertRealParent(resolved);
const lockPath = `${resolved}.lock`;
let lock: fs.FileHandle | undefined;
let temporary: string | undefined;
let key: Buffer | undefined;
let contents: Buffer | undefined;
try {
lock = await fs.open(
lockPath,
constants.O_CREAT |
constants.O_EXCL |
constants.O_WRONLY |
(constants.O_NOFOLLOW ?? 0),
0o600,
);
await lock.sync();
const current = await readManifest(resolved);
if (current.activeKeyId !== options.expectedActiveKeyId) {
throw new LocalSecretKeyringConflictError();
}
if (Object.keys(current.keys).length >= MAX_KEY_COUNT) {
throw new LocalSecretUnavailableError();
}
const keyId = newKeyId();
key = randomBytes(32);
const next: LocalSecretKeyringManifest = Object.freeze({
version: 1,
activeKeyId: keyId,
keys: Object.freeze({
...current.keys,
[keyId]: key.toString('base64url'),
}),
});
contents = canonicalManifest(next);
temporary = await writeTemporary(resolved, contents);
await fs.rename(temporary, resolved);
temporary = undefined;
await syncDirectory(path.dirname(resolved));
return summary(next);
} catch (error) {
if (error instanceof LocalSecretKeyringConflictError) throw error;
throw new LocalSecretUnavailableError();
} finally {
key?.fill(0);
contents?.fill(0);
if (temporary) await fs.unlink(temporary).catch(() => undefined);
await lock?.close().catch(() => undefined);
if (lock) await fs.unlink(lockPath).catch(() => undefined);
}
}
@@ -0,0 +1,321 @@
import { normalizeLocalDispatchCandidate } from '@qinglong/runtime-core/local-dispatch';
import {
LOCAL_SECRET_ALGORITHM,
MAX_LOCAL_SECRET_BATCH_SIZE,
LocalSecretMutationConflictError,
LocalSecretUnavailableError,
LocalSecretVersionConflictError,
assertLocalSecretExpectedVersion,
assertLocalSecretMutationId,
assertLocalSecretName,
assertLocalSecretPlaintext,
assertLocalSecretProjectId,
createLocalSecretRef,
parseLocalSecretRef,
type LocalSecretEnvelope,
type LocalSecretEnvelopeRepository,
type LocalSecretEnvironmentProvider,
type LocalSecretKeyProvider,
type PutEncryptedLocalSecretCommand,
type PutEncryptedLocalSecretResult,
} from '@qinglong/runtime-core/local-secret';
import {
decodeLocalSecretPlaintext,
decryptLocalSecretEnvelopeToBuffer,
encryptLocalSecretEnvelope,
type LocalSecretNonceFactory,
} from './crypto';
import {
localSecretPlaintextMatches,
ownedLocalSecretKeyMaterial,
} from './keyMaterial';
function exactKeys(value: object, expected: readonly string[]): boolean {
const actual = Object.keys(value).sort();
const sortedExpected = [...expected].sort();
return (
actual.length === sortedExpected.length &&
actual.every((key, index) => key === sortedExpected[index])
);
}
function assertPutCommand(command: PutEncryptedLocalSecretCommand): void {
if (
!command ||
typeof command !== 'object' ||
Array.isArray(command) ||
!exactKeys(command, [
'projectId',
'name',
'plaintext',
'mutationId',
'expectedCurrentVersion',
'createdAtMs',
])
) {
throw new TypeError('Local Secret write command is invalid');
}
assertLocalSecretProjectId(command.projectId);
assertLocalSecretName(command.name);
assertLocalSecretPlaintext(command.plaintext);
assertLocalSecretMutationId(command.mutationId);
assertLocalSecretExpectedVersion(command.expectedCurrentVersion);
if (!Number.isSafeInteger(command.createdAtMs) || command.createdAtMs < 0) {
throw new TypeError('Local Secret creation time is invalid');
}
}
export interface LocalProjectSecretMaterialRequest {
readonly projectId: string;
readonly secretRef: string;
readonly signal?: AbortSignal;
}
export interface LocalProjectSecretMaterial {
readonly secretRef: string;
readonly bytes: Uint8Array;
dispose(): void | Promise<void>;
}
export class EncryptedLocalSecretService
implements LocalSecretEnvironmentProvider
{
constructor(
private readonly envelopes: LocalSecretEnvelopeRepository,
private readonly keys: LocalSecretKeyProvider,
private readonly nonceFactory?: LocalSecretNonceFactory,
) {}
async put(
command: PutEncryptedLocalSecretCommand,
): Promise<PutEncryptedLocalSecretResult> {
assertPutCommand(command);
try {
return await this.putValidated(command);
} catch (error) {
if (
error instanceof LocalSecretVersionConflictError ||
error instanceof LocalSecretMutationConflictError ||
error instanceof LocalSecretUnavailableError
) {
throw error;
}
throw new LocalSecretUnavailableError();
}
}
private async putValidated(
command: PutEncryptedLocalSecretCommand,
): Promise<PutEncryptedLocalSecretResult> {
const existing = await this.envelopes.findLocalSecretEnvelopeByMutation(
command.projectId,
command.name,
command.mutationId,
);
if (existing) {
const material = ownedLocalSecretKeyMaterial(
await this.keys.resolve(existing.keyId),
existing.keyId,
);
try {
if (
existing.version !== command.expectedCurrentVersion + 1 ||
!localSecretPlaintextMatches(
existing,
material.key,
command.plaintext,
)
) {
throw new LocalSecretMutationConflictError();
}
} finally {
material.key.fill(0);
}
return this.result('existing', existing);
}
const material = ownedLocalSecretKeyMaterial(await this.keys.active());
try {
const envelope = encryptLocalSecretEnvelope(
{
projectId: command.projectId,
name: command.name,
version: command.expectedCurrentVersion + 1,
mutationId: command.mutationId,
keyId: material.keyId,
algorithm: LOCAL_SECRET_ALGORITHM,
createdAtMs: command.createdAtMs,
},
command.plaintext,
material.key,
this.nonceFactory,
);
const appended = await this.envelopes.appendLocalSecretEnvelope({
envelope,
expectedCurrentVersion: command.expectedCurrentVersion,
});
if (appended.status === 'existing') {
const existingMaterial =
appended.envelope.keyId === material.keyId
? material
: ownedLocalSecretKeyMaterial(
await this.keys.resolve(appended.envelope.keyId),
appended.envelope.keyId,
);
try {
if (
appended.envelope.version !== command.expectedCurrentVersion + 1 ||
!localSecretPlaintextMatches(
appended.envelope,
existingMaterial.key,
command.plaintext,
)
) {
throw new LocalSecretMutationConflictError();
}
} finally {
if (existingMaterial !== material) existingMaterial.key.fill(0);
}
}
return this.result(appended.status, appended.envelope);
} finally {
material.key.fill(0);
}
}
async resolveLocalSecretEnvironment(request: {
readonly candidate: Parameters<
LocalSecretEnvironmentProvider['resolveLocalSecretEnvironment']
>[0]['candidate'];
readonly secretRefs: readonly string[];
}): Promise<readonly string[] | null> {
const cachedKeys = new Map<string, Buffer>();
try {
if (!request || typeof request !== 'object' || Array.isArray(request)) {
throw new LocalSecretUnavailableError();
}
const candidate = normalizeLocalDispatchCandidate(request.candidate);
if (
!Array.isArray(request.secretRefs) ||
request.secretRefs.length > MAX_LOCAL_SECRET_BATCH_SIZE
) {
throw new LocalSecretUnavailableError();
}
const references = request.secretRefs.map(parseLocalSecretRef);
if (
references.some(
(reference) => reference.projectId !== candidate.projectId,
)
) {
throw new LocalSecretUnavailableError();
}
const envelopes = await this.envelopes.resolveLocalSecretEnvelopes(
references,
);
if (
envelopes.length !== references.length ||
envelopes.some((item) => item === null)
) {
return null;
}
const plaintext: string[] = [];
for (const envelope of envelopes as readonly LocalSecretEnvelope[]) {
let key = cachedKeys.get(envelope.keyId);
if (!key) {
const material = ownedLocalSecretKeyMaterial(
await this.keys.resolve(envelope.keyId),
envelope.keyId,
);
key = material.key;
cachedKeys.set(envelope.keyId, key);
}
const bytes = decryptLocalSecretEnvelopeToBuffer(envelope, key);
try {
plaintext.push(decodeLocalSecretPlaintext(bytes));
} finally {
bytes.fill(0);
}
}
return Object.freeze(plaintext);
} catch (error) {
if (error instanceof LocalSecretUnavailableError) throw error;
throw new LocalSecretUnavailableError();
} finally {
for (const key of cachedKeys.values()) key.fill(0);
cachedKeys.clear();
}
}
async resolveProjectSecretMaterial(
request: Readonly<LocalProjectSecretMaterialRequest>,
): Promise<Readonly<LocalProjectSecretMaterial> | null> {
let key: Buffer | undefined;
let plaintext: Buffer | undefined;
try {
if (
!request ||
typeof request !== 'object' ||
Array.isArray(request) ||
!exactKeys(
request,
request.signal === undefined
? ['projectId', 'secretRef']
: ['projectId', 'secretRef', 'signal'],
) ||
(request.signal !== undefined &&
typeof request.signal.aborted !== 'boolean') ||
request.signal?.aborted
) {
throw new LocalSecretUnavailableError();
}
assertLocalSecretProjectId(request.projectId);
const reference = parseLocalSecretRef(request.secretRef);
if (reference.projectId !== request.projectId) {
throw new LocalSecretUnavailableError();
}
const [envelope] = await this.envelopes.resolveLocalSecretEnvelopes([
reference,
]);
if (!envelope) return null;
const material = ownedLocalSecretKeyMaterial(
await this.keys.resolve(envelope.keyId),
envelope.keyId,
);
key = material.key;
plaintext = decryptLocalSecretEnvelopeToBuffer(envelope, key);
const ownedPlaintext = plaintext;
plaintext = undefined;
let disposed = false;
return Object.freeze({
secretRef: request.secretRef,
bytes: ownedPlaintext,
dispose(): void {
if (disposed) return;
disposed = true;
ownedPlaintext.fill(0);
},
});
} catch (error) {
plaintext?.fill(0);
if (error instanceof LocalSecretUnavailableError) throw error;
throw new LocalSecretUnavailableError();
} finally {
key?.fill(0);
}
}
private result(
status: PutEncryptedLocalSecretResult['status'],
envelope: LocalSecretEnvelope,
): PutEncryptedLocalSecretResult {
return Object.freeze({
status,
version: envelope.version,
secretRef: createLocalSecretRef({
projectId: envelope.projectId,
name: envelope.name,
version: envelope.version,
}),
});
}
}