sec(inso): Added secure wrapper for inso.exe

This commit is contained in:
Kyle
2026-06-12 13:10:59 -04:00
parent b7774b0173
commit b3ad56ca7a
7 changed files with 196 additions and 3 deletions

View File

@@ -243,13 +243,18 @@ jobs:
env:
VERSION: ${{ env.INSO_VERSION }}
- name: Compile inso secure wrapper (Windows only)
if: runner.os == 'Windows'
shell: bash
run: ./build-inso-wrapper.sh
- name: Code-sign inso exe (Windows only)
if: runner.os == 'Windows'
uses: digicert/code-signing-software-trust-action@fae23a455ba4bde62b64fd7cb2f81ade788f5a95 # v1.2.1
with:
simple-signing-mode: true
# If the below 2 parameters are supplied, then smctl executable is invoked to attempt the signing.
input: packages/insomnia-inso/binaries/inso.exe
# Sign the directory so both inso.exe (wrapper) and inso-node.exe (inner) are signed.
input: packages/insomnia-inso/binaries
keypair-alias: ${{ secrets.DIGICERT_KEYPAIR_ALIAS }}
env:
SM_HOST: ${{ vars.DIGICERT_SM_HOST }}

43
build-inso-wrapper.sh Executable file
View File

@@ -0,0 +1,43 @@
# Wraps the pkg-bundled inso.exe with a C++ launcher that applies Windows process
# mitigation policies (ProcessImageLoadPolicy PreferSystem32Images=1) to prevent
# DLL sideloading. See packages/insomnia-inso/src/cpp/ for the wrapper source.
set -e
VERSION=$(jq .version ./packages/insomnia-inso/package.json -rj)
echo "Starting inso secure wrapper build for version $VERSION..."
MAJOR=$(echo $VERSION | cut -d '.' -f 1)
MINOR=$(echo $VERSION | cut -d '.' -f 2)
PATCH=$(echo $VERSION | cut -d '.' -f 3 | cut -d '-' -f 1)
TAG=$(echo $VERSION | cut -d '-' -f 2)
CPP_DIR=packages/insomnia-inso/src/cpp
BINARIES_DIR=packages/insomnia-inso/binaries
if [ -n "$TAG" ]; then
TAG="-$TAG"
fi
# Rename the pkg-built binary so the wrapper can take the inso.exe name.
echo "Renaming inso.exe to inso-node.exe..."
mv $BINARIES_DIR/inso.exe $BINARIES_DIR/inso-node.dll
echo "Injecting version strings..."
sed "s/__MAJOR__/$MAJOR/g" $CPP_DIR/resources.rc > $CPP_DIR/final.rc
sed -i "s/__MINOR__/$MINOR/g" $CPP_DIR/final.rc
sed -i "s/__PATCH__/$PATCH/g" $CPP_DIR/final.rc
sed -i "s/__TAG__/$TAG/g" $CPP_DIR/final.rc
sed -i "s/__YEAR__/$(date +%Y)/g" $CPP_DIR/final.rc
echo "Compiling resources..."
windres $CPP_DIR/final.rc $CPP_DIR/res.o
echo "Compiling inso wrapper..."
# Note: no -mwindows flag — inso is a console application.
g++ -lkernel32 -c $CPP_DIR/inso.cpp -o $CPP_DIR/inso.o
echo "Linking inso wrapper..."
g++ -O2 -static -static-libgcc -static-libstdc++ -lwinpthread \
$CPP_DIR/inso.o $CPP_DIR/res.o -o $BINARIES_DIR/inso.exe
echo "Inso secure wrapper built successfully."

View File

@@ -0,0 +1,95 @@
// NOTE: The calls in this wrapper are only supported on Windows >= 8.
#define _WIN32_WINNT 0x602
#include <string>
#include <windows.h>
int main() {
// Apply process mitigation policies before any further DLL loading occurs.
// PreferSystem32Images is inherited by child processes, so inso-node.exe is
// also protected without needing its own policy setup.
::PROCESS_MITIGATION_POLICY psp = ::ProcessSignaturePolicy;
::PROCESS_MITIGATION_POLICY pilp = ::ProcessImageLoadPolicy;
::PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY pmbsp;
::PROCESS_MITIGATION_IMAGE_LOAD_POLICY pmilp;
if (!::GetProcessMitigationPolicy(::GetCurrentProcess(), psp, &pmbsp, sizeof(pmbsp))) {
::WriteFile(::GetStdHandle(STD_ERROR_HANDLE),
"inso: could not get ProcessSignaturePolicy\n", 44, NULL, NULL);
return 1;
}
if (pmbsp.MitigationOptIn == 0) {
pmbsp.MitigationOptIn = 1;
if (!::SetProcessMitigationPolicy(psp, &pmbsp, sizeof(pmbsp))) {
::WriteFile(::GetStdHandle(STD_ERROR_HANDLE),
"inso: could not set ProcessSignaturePolicy\n", 44, NULL, NULL);
return 1;
}
}
if (!::GetProcessMitigationPolicy(::GetCurrentProcess(), pilp, &pmilp, sizeof(pmilp))) {
::WriteFile(::GetStdHandle(STD_ERROR_HANDLE),
"inso: could not get ProcessImageLoadPolicy\n", 44, NULL, NULL);
return 1;
}
if (pmilp.PreferSystem32Images == 0) {
pmilp.PreferSystem32Images = 1;
if (!::SetProcessMitigationPolicy(pilp, &pmilp, sizeof(pmilp))) {
::WriteFile(::GetStdHandle(STD_ERROR_HANDLE),
"inso: could not set ProcessImageLoadPolicy\n", 44, NULL, NULL);
return 1;
}
}
// Resolve the path to inso-node.exe (same directory as this wrapper).
wchar_t wrapperPath[MAX_PATH];
::GetModuleFileNameW(NULL, wrapperPath, MAX_PATH);
std::wstring dir(wrapperPath);
dir = dir.substr(0, dir.find_last_of(L"\\"));
std::wstring innerExe = dir + L"\\inso-node.dll";
// Rebuild the command line, replacing argv[0] (this wrapper) with inso-node.exe.
// GetCommandLineW() returns the full command line including the wrapper's path,
// which may be quoted. We skip past that first token to get the remaining args.
LPWSTR origCmd = ::GetCommandLineW();
LPWSTR argsStart = origCmd;
if (*argsStart == L'"') {
argsStart++; // skip opening quote
while (*argsStart && *argsStart != L'"') argsStart++;
if (*argsStart == L'"') argsStart++; // skip closing quote
} else {
while (*argsStart && *argsStart != L' ') argsStart++;
}
// argsStart now points to " arg1 arg2 ..." or an empty string.
std::wstring cmdLine = L"\"" + innerExe + L"\"" + std::wstring(argsStart);
::STARTUPINFOW si;
::PROCESS_INFORMATION pi;
::ZeroMemory(&si, sizeof(si));
::ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);
si.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
if (!::CreateProcessW(NULL, &cmdLine[0], NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
const char *msg = "inso: could not launch inso-node.dll\n";
::WriteFile(::GetStdHandle(STD_ERROR_HANDLE), msg, 37, NULL, NULL);
return 1;
}
::WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitCode = 1;
::GetExitCodeProcess(pi.hProcess, &exitCode);
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
return static_cast<int>(exitCode);
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- windows server 2012 R2 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<maxversiontested Id="10.0.18362.0"/>
</application>
</compatibility>
</assembly>

View File

View File

@@ -0,0 +1,29 @@
#include "resource.h"
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
1 VERSIONINFO
FILEVERSION __MAJOR__,__MINOR__,__PATCH__,0
PRODUCTVERSION __MAJOR__,__MINOR__,__PATCH__,0
FILEOS 0x40004
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
BLOCK "040904B0"
{
VALUE "CompanyName", "Kong"
VALUE "FileDescription", "inso"
VALUE "FileVersion", "__MAJOR__.__MINOR__.__PATCH____TAG__"
VALUE "InternalName", "inso"
VALUE "LegalCopyright", "Copyright \xA9 __YEAR__ Kong"
VALUE "OriginalFilename", ""
VALUE "ProductName", "inso"
VALUE "ProductVersion", "__MAJOR__.__MINOR__.__PATCH__.0"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409, 0x04B0
}
}
1 MANIFEST "manifest.txt"

View File

@@ -24,7 +24,7 @@ const spawnCompressProcess = (cwd: ProcessEnvOptions['cwd']) => {
'../binaries',
platform === 'win32' ? '-a -cf' : '-cJf',
platform === 'win32' ? `inso-windows-${version}.zip` : `inso-linux-${process.arch}-${version}.tar.xz`,
platform === 'win32' ? 'inso.exe' : 'inso',
...(platform === 'win32' ? ['inso.exe', 'inso-node.dll'] : ['inso']),
],
{ cwd, shell: platform === 'win32' },
);