
Research
/Security News
Critical Vulnerability in NestJS Devtools: Localhost RCE via Sandbox Escape
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
Gotenberg.Sharp.API.Client.Framsikt
Advanced tools
C# API client for interacting with the Gotenberg v7 & v8 micro-service's API, a docker-powered stateless API for converting & merging HTML, Markdown and Office documents to PDF. The client supports a configurable Polly retry policy with exponential back-off for handling transient exceptions.
⭐ For Gotenberg v7 & v8 ⭐
.NET C# Client for interacting with the Gotenberg v7 & v8 micro-service's API. Gotenberg is a Docker-powered stateless API for converting & merging HTML, Markdown and Office documents to PDF. The client supports a configurable Polly retry policy with exponential backoff for handling transient exceptions.
Pull the image from dockerhub.com
> docker pull gotenberg/gotenberg:latest
Create & start a container
docker run --name gotenbee8x --rm -p 3000:3000 gotenberg/gotenberg:latest gotenberg --api-timeout=1800s --log-level=debug
Install nuget package into your project
PM> Install-Package Gotenberg.Sharp.Api.Client
Note: Use v1.x nugets for Gotenberg v6.
"GotenbergSharpClient": {
"ServiceUrl": "http://localhost:3000",
"HealthCheckUrl": "http://localhost:3000/health",
"RetryPolicy": {
"Enabled": true,
"RetryCount": 4,
"BackoffPower": 1.5,
"LoggingEnabled": true
}
}
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddOptions<GotenbergSharpClientOptions>()
.Bind(Configuration.GetSection(nameof(GotenbergSharpClient)));
services.AddGotenbergSharpClient();
.....
}
See the linqPad folder for complete examples.
UrlScreenshot.linq
, HtmlScreenshot.linq
UrlHtmlRendering.linq
, HtmlFileRendering.linq
With embedded assets:
[HttpGet]
public async Task<ActionResult> HtmlToPdf([FromServices] GotenbergSharpClient sharpClient)
{
var builder = new HtmlRequestBuilder()
.AddDocument(doc =>
doc.SetBody(GetBody()).SetFooter(GetFooter())
).WithPageProperties(pp =>
{
pp.SetPaperSize(PaperSizes.A3)
.SetMargins(Margins.None)
.SetScale(.99);
}).WithAsyncAssets(async assets => assets.AddItem("some-image.jpg", await GetImageBytes()));
var req = await builder.BuildAsync();
var result = await sharpClient.HtmlToPdfAsync(req);
return this.File(result, "application/pdf", "gotenbergFromHtml.pdf");
}
Url to Pdf with custom page range, header & footer:
public async Task<Stream> CreateFromUrl(string headerPath, string footerPath)
{
var builder = new UrlRequestBuilder()
.SetUrl("https://www.cnn.com")
.ConfigureRequest(config =>
{
config.SetPageRanges("1-2");
})
.AddAsyncHeaderFooter(async
doc => doc.SetHeader(await File.ReadAllTextAsync(headerPath))
.SetFooter(await File.ReadAllBytesAsync(footerPath)
)).WithPageProperties(pp =>
{
pp.SetPaperSize(PaperSizes.A4)
.SetMargins(Margins.None)
.SetScale(.90)
.SetLandscape();
});
var request = await builder.BuildAsync();
return await _sharpClient.UrlToPdfAsync(request);
}
Capture screenshots from URLs or HTML content:
public async Task<Stream> CreateUrlScreenshot()
{
var builder = new UrlScreenshotRequestBuilder()
.SetUrl("https://www.example.com")
.SetScreenshotBehaviors(b =>
{
b.SetDimensions(1920, 1080)
.SetFormat(ScreenshotImageFormat.Png)
.SetClip(true)
.SetOmitBackground(false);
})
.SetConversionBehaviors(b =>
{
b.EmulateAsScreen()
.AddAdditionalHeaders("User-Agent", "GotenbergScreenshotBot/1.0");
});
var request = await builder.BuildAsync();
return await _sharpClient.UrlToScreenshotAsync(request);
}
public async Task<Stream> CreateHtmlScreenshot()
{
var builder = new HtmlScreenshotRequestBuilder()
.AddAsyncDocument(async doc =>
doc.SetBody(await GetHtmlContentAsync())
)
.SetScreenshotBehaviors(b =>
{
b.SetDimensions(1200, 800)
.SetFormat(ScreenshotImageFormat.Jpeg)
.SetQuality(90)
.SetOptimizeForSpeed(true);
})
.WithAsyncAssets(async assets =>
assets.AddItems(await GetAssetFilesAsync())
);
var request = await builder.BuildAsync();
return await _sharpClient.HtmlToScreenshotAsync(request);
}
Capture fully-rendered HTML after JavaScript execution:
public async Task<Stream> RenderUrlToHtml()
{
var builder = new UrlHtmlRenderingRequestBuilder()
.SetUrl("https://jsonplaceholder.typicode.com/posts/1")
.SetConversionBehaviors(b =>
{
b.EmulateAsScreen()
.SetBrowserWaitDelay(2) // Wait for dynamic content
.AddAdditionalHeaders("Accept", "application/json,text/html,*/*");
});
var request = await builder.BuildAsync();
return await _sharpClient.UrlToHtmlAsync(request);
}
public async Task<Stream> RenderMarkdownToHtml()
{
var builder = new HtmlFileRenderingRequestBuilder(containsMarkdown: true)
.AddDocument(doc =>
doc.SetBody("# Dynamic Content\n\nThis **markdown** will be rendered.")
.SetContainsMarkdown(true)
)
.WithAssets(assets =>
{
assets.AddItem("styles.css", "body { font-family: Arial; }");
})
.SetConversionBehaviors(b =>
{
b.EmulateAsScreen()
.SetBrowserWaitExpression("document.readyState === 'complete'");
});
var request = await builder.BuildAsync();
return await _sharpClient.HtmlFileToHtmlAsync(request);
}
Merges office documents and configures the request time-out:
public async Task<Stream> DoOfficeMerge(string sourceDirectory)
{
var builder = new MergeOfficeBuilder()
.WithAsyncAssets(async a => a.AddItems(await GetDocsAsync(sourceDirectory)));
var request = await builder.BuildAsync();
return await _sharpClient.MergeOfficeDocsAsync(request);
}
Markdown to Pdf conversion with embedded assets:
public async Task<Stream> CreateFromMarkdown()
{
var builder = new HtmlRequestBuilder()
.AddAsyncDocument(async
doc => doc.SetHeader(await this.GetHeaderAsync())
.SetBody(await GetBodyAsync())
.SetContainsMarkdown()
.SetFooter(await GetFooterAsync())
).WithPageProperties(pp =>
{
pp.UseChromeDefaults().SetLandscape().SetScale(.90);
}).WithAsyncAssets(async
a => a.AddItems(await GetMarkdownAssets())
);
var request = await builder.BuildAsync();
return await _sharpClient.HtmlToPdfAsync(request);
}
All request types support webhooks
public async Task SendUrlToWebhookEndpoint(string headerPath, string footerPath)
{
var builder = new UrlRequestBuilder()
.SetUrl("https://www.cnn.com")
.ConfigureRequest(reqBuilder =>
{
reqBuilder.AddWebhook(hook =>
{
hook.SetUrl("http://host.docker.internal:5000/api/your/webhookReceiver")
.SetErrorUrl("http://host.docker.internal:5000/api/your/webhookReceiver/error")
.AddExtraHeader("custom-header", "value");
})
.SetPageRanges("1-2");
})
.AddAsyncHeaderFooter(async
b => b.SetHeader(await System.IO.File.ReadAllTextAsync(headerPath))
.SetFooter(await System.IO.File.ReadAllBytesAsync(footerPath))
).WithPageProperties(pp =>
{
pp.SetPaperSize(PaperSizes.A4)
.SetMargins(Margins.None)
.SetScale(.90)
.SetLandscape();
});
var request = await builder.BuildAsync();
await _sharpClient.FireWebhookAndForgetAsync(request);
}
Builds a 30 page pdf by merging the front two pages of 15 news sites. Takes about a minute to complete
public async Task<Stream> CreateWorldNewsSummary()
{
var sites = new[]
{
"https://www.nytimes.com", "https://www.axios.com/", "https://www.csmonitor.com",
"https://www.wsj.com", "https://www.usatoday.com", "https://www.irishtimes.com",
"https://www.lemonde.fr", "https://calgaryherald.com", "https://www.bbc.com/news/uk",
"https://www.thehindu.com", "https://www.theaustralian.com.au",
"https://www.welt.de", "https://www.cankaoxiaoxi.com",
"https://www.novinky.cz", "https://www.elobservador.com.uy"
}
.Select(u => new Uri(u));
var builders = CreateBuilders(sites);
var requests = builders.Select(b => b.Build());
return await ExecuteRequestsAndMerge(requests);
}
IEnumerable<UrlRequestBuilder> CreateBuilders(IEnumerable<Uri> uris)
{
foreach (var uri in uris)
{
yield return new UrlRequestBuilder()
.SetUrl(uri)
.ConfigureRequest(req => { req.SetPageRanges("1-2"); })
.AddHeaderFooter(docBuilder =>
{
docBuilder.SetHeader(GetHeadFoot(uri.Host.Replace("www.", string.Empty).ToUpper()))
.SetFooter(GetHeadFoot(uri.ToString()));
})
.WithPageProperties(pp =>
{
pp.UseChromeDefaults()
.SetScale(.90)
.SetLandscape()
.SetMarginLeft(.5)
.SetMarginRight(.5);
});
}
static string GetHeadFoot(string heading)
=> "<html><head> <style> body { font-size: 8rem; } h1 { margin-left: auto; margin-right: auto; } </style></head><body><h1>" +
heading + "</h1></body></html>";
}
async Task<Stream> ExecuteRequestsAndMerge(IEnumerable<UrlRequest> requests)
{
var tasks = requests.Select(r => _sharpClient.UrlToPdfAsync(r));
var results = await Task.WhenAll(tasks);
var mergeBuilder = new MergeBuilder()
.WithAssets(b => {
b.AddItems(results.Select((r, i) => KeyValuePair.Create($"{i}.pdf", r)));
});
var request = mergeBuilder.Build();
return await _sharpClient.MergePdfsAsync(request);
}
FAQs
C# API client for interacting with the Gotenberg v7 & v8 micro-service's API, a docker-powered stateless API for converting & merging HTML, Markdown and Office documents to PDF. The client supports a configurable Polly retry policy with exponential back-off for handling transient exceptions.
We found that gotenberg.sharp.api.client.framsikt demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
Product
Customize license detection with Socket’s new license overlays: gain control, reduce noise, and handle edge cases with precision.
Product
Socket now supports Rust and Cargo, offering package search for all users and experimental SBOM generation for enterprise projects.