feat(server): enhance secret management and logging functionality

- Added SCRUNNER_SECRETS_KEY to README as a required variable for production.
- Implemented redaction of sensitive information in logs across various components.
- Enhanced script execution functions to include an owner parameter for better secret management.
- Introduced a secrets API in the script sandbox for retrieving and managing secrets.
- Updated UI components to support owner selection for script execution and secret management.
This commit is contained in:
2026-08-14 22:11:03 +07:00
parent 94aa5b2efa
commit 3e4d5f443d
19 changed files with 886 additions and 39 deletions
+67
View File
@@ -0,0 +1,67 @@
import * as fsStore from "../../fs-store.js";
import {
assertSecretName,
deleteSecret,
getSecretById,
listSecrets,
upsertSecret,
} from "../../secrets-store.js";
import { MIN_SECRET_LENGTH } from "../../secret-value.js";
/**
* @param {import("fastify").FastifyInstance} fastify
*/
export default async function secretsPlugin(fastify) {
fastify.addHook("onRequest", fastify.requireAdmin);
fastify.get("/secrets", async (req, reply) => {
const q = /** @type {{ owner?: string }} */ (req.query ?? {});
try {
const owner = q.owner ? fsStore.assertOwner(q.owner) : undefined;
const secrets = await listSecrets({ owner });
return { secrets };
} catch (err) {
return reply.code(err.statusCode ?? 500).send({ error: err.message });
}
});
fastify.put("/secrets", async (req, reply) => {
const body = /** @type {{ owner?: string, name?: string, value?: string }} */ (
req.body ?? {}
);
try {
fsStore.assertOwner(String(body.owner ?? ""));
assertSecretName(String(body.name ?? ""));
} catch (err) {
return reply.code(err.statusCode ?? 400).send({ error: err.message });
}
const value = String(body.value ?? "");
if (value.length < MIN_SECRET_LENGTH) {
return reply
.code(400)
.send({ error: `value must be at least ${MIN_SECRET_LENGTH} characters` });
}
try {
const secret = await upsertSecret({
owner: String(body.owner),
name: String(body.name),
value,
});
return reply.send({ secret });
} catch (err) {
return reply.code(err.statusCode ?? 500).send({ error: err.message });
}
});
fastify.delete("/secrets/:id", async (req, reply) => {
const { id } = /** @type {{ id: string }} */ (req.params);
const existing = await getSecretById(id);
if (!existing) {
return reply.code(404).send({ error: "secret not found" });
}
await deleteSecret(id);
return { ok: true };
});
}