solana/explorer/src/utils/security-txt.ts

101 lines
2.5 KiB
TypeScript

import { ProgramDataAccountInfo } from "validators/accounts/upgradeable-program";
export type SecurityTXT = {
name: string;
project_url: string;
contacts: string;
policy: string;
preferred_languages?: string;
source_code?: string;
encryption?: string;
auditors?: string;
acknowledgements?: string;
expiry?: string;
};
const REQUIRED_KEYS: (keyof SecurityTXT)[] = [
"name",
"project_url",
"contacts",
"policy",
];
const VALID_KEYS: (keyof SecurityTXT)[] = [
"name",
"project_url",
"contacts",
"policy",
"preferred_languages",
"source_code",
"encryption",
"auditors",
"acknowledgements",
"expiry",
];
const HEADER = "=======BEGIN SECURITY.TXT V1=======\0";
const FOOTER = "=======END SECURITY.TXT V1=======\0";
export const fromProgramData = (
programData: ProgramDataAccountInfo
): { securityTXT?: SecurityTXT; error?: string } => {
const [data, encoding] = programData.data;
if (!(data && encoding === "base64"))
return { securityTXT: undefined, error: "Failed to decode program data" };
const decoded = Buffer.from(data, encoding);
const headerIdx = decoded.indexOf(HEADER);
const footerIdx = decoded.indexOf(FOOTER);
if (headerIdx < 0 || footerIdx < 0) {
return { securityTXT: undefined, error: "Program has no security.txt" };
}
/*
the expected structure of content should be a list
of ascii encoded key value pairs separated by null characters.
e.g. key1\0value1\0key2\0value2\0
*/
const content = decoded.subarray(headerIdx + HEADER.length, footerIdx);
const map = content
.reduce<number[][]>(
(prev, current) => {
if (current === 0) {
prev.push([]);
} else {
prev[prev.length - 1].push(current);
}
return prev;
},
[[]]
)
.map((c) => String.fromCharCode(...c))
.reduce<{ map: { [key: string]: string }; key: string | undefined }>(
(prev, current) => {
const key = prev.key;
if (!key) {
return {
map: prev.map,
key: current,
};
} else {
return {
map: {
...(VALID_KEYS.some((x) => x === key) ? { [key]: current } : {}),
...prev.map,
},
key: undefined,
};
}
},
{ map: {}, key: undefined }
).map;
if (!REQUIRED_KEYS.every((k) => k in map)) {
return {
securityTXT: undefined,
error: `some required fields (${REQUIRED_KEYS}) are missing`,
};
}
return { securityTXT: map as SecurityTXT, error: undefined };
};