Skip to main content 首页 创作者 dicklesworthstone pi_agent_rust documenso-security-basics
documenso-security-basics Implement security best practices for Documenso document signing integrations.
Use when securing API keys, configuring webhooks securely,
or implementing document security measures.
Trigger with phrases like "documenso security", "secure documenso",
"documenso API key security", "documenso webhook security".
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Dicklesworthstone/pi_agent_rust --skill documenso-security-basics命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... name documenso-security-basics description Implement security best practices for Documenso document signing integrations.
Use when securing API keys, configuring webhooks securely,
or implementing document security measures.
Trigger with phrases like "documenso security", "secure documenso",
"documenso API key security", "documenso webhook security".
allowed-tools Read, Write, Edit version 1.0.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io>
Documenso Security Basics
Overview
Essential security practices for Documenso integrations including API key management, webhook security, and document protection.
Prerequisites
Documenso account with API access
Understanding of environment variables
Basic security concepts
Instructions
Step 1: Secure API Key Management
client = ({
: ,
});
client = ({
: process. . ?? ,
});
( ): {
required = [ ];
missing = required. ( !process. [key]);
(missing. > ) {
(
);
}
apiKey = process. . !;
(!apiKey. ( )) {
. ( );
}
}
const
new
Documenso
apiKey
"dcs_abc123..."
const
new
Documenso
apiKey
env
DOCUMENSO_API_KEY
""
function
validateEnvironment
void
const
"DOCUMENSO_API_KEY"
const
filter
(key ) =>
env
if
length
0
throw
new
Error
`Missing required environment variables: ${missing.join(", " )} `
const
env
DOCUMENSO_API_KEY
if
startsWith
"dcs_"
console
warn
"Warning: API key format unexpected (should start with dcs_)"
Step 2: API Key Rotation
interface KeyRotationConfig {
primaryKey : string ;
secondaryKey ?: string ;
}
async function getClientWithFallback (
config : KeyRotationConfig
): Promise <Documenso > {
try {
const client = new Documenso ({ apiKey : config.primaryKey });
await client.documents .findV0 ({ perPage : 1 });
return client;
} catch (error : any ) {
if (error.statusCode === 401 && config.secondaryKey ) {
console .warn ("Primary key failed, trying secondary..." );
return new Documenso ({ apiKey : config.secondaryKey });
}
throw error;
}
}
Step 3: Webhook Security import express from "express" ;
const app = express ();
app.use ("/webhooks/documenso" , express.raw ({ type : "application/json" }));
app.post ("/webhooks/documenso" , (req, res ) => {
const receivedSecret = req.headers ["x-documenso-secret" ];
const expectedSecret = process.env .DOCUMENSO_WEBHOOK_SECRET ;
if (!expectedSecret) {
console .error ("DOCUMENSO_WEBHOOK_SECRET not configured" );
return res.status (500 ).json ({ error : "Webhook not configured" });
}
if (!timingSafeEqual (receivedSecret as string , expectedSecret)) {
console .warn ("Invalid webhook secret received" );
return res.status (401 ).json ({ error : "Invalid signature" });
}
try {
const payload = JSON .parse (req.body .toString ());
handleWebhookEvent (payload);
res.status (200 ).json ({ received : true });
} catch (error) {
console .error ("Webhook processing error:" , error);
res.status (400 ).json ({ error : "Invalid payload" });
}
});
function timingSafeEqual (a : string , b : string ): boolean {
if (a.length !== b.length ) return false ;
const bufA = Buffer .from (a);
const bufB = Buffer .from (b);
return require ("crypto" ).timingSafeEqual (bufA, bufB);
}
Step 4: Document Access Control
interface DocumentAccess {
documentId : string ;
ownerId : string ;
authorizedEmails : string [];
}
class DocumentAccessControl {
private accessMap = new Map <string , DocumentAccess >();
async createDocument (
userId : string ,
title : string ,
authorizedEmails : string []
): Promise <string > {
const doc = await client.documents .createV0 ({ title });
const documentId = doc.documentId !;
this .accessMap .set (documentId, {
documentId,
ownerId : userId,
authorizedEmails,
});
return documentId;
}
canAccess (userId : string , documentId : string ): boolean {
const access = this .accessMap .get (documentId);
if (!access) return false ;
return access.ownerId === userId;
}
canSign (email : string , documentId : string ): boolean {
const access = this .accessMap .get (documentId);
if (!access) return false ;
return access.authorizedEmails .includes (email.toLowerCase ());
}
}
Step 5: Signing URL Security
function logDocument (doc : any ): void {
const safeDoc = { ...doc };
if (safeDoc.recipients ) {
safeDoc.recipients = safeDoc.recipients .map ((r : any ) => ({
...r,
signingUrl : "[REDACTED]" ,
signingToken : "[REDACTED]" ,
}));
}
console .log (JSON .stringify (safeDoc, null , 2 ));
}
async function getSecureSigningSession (
documentId : string ,
recipientEmail : string
): Promise <{ signingUrl : string ; expiresAt : Date }> {
const doc = await client.documents .getV0 ({ documentId });
const recipient = doc.recipients ?.find ((r ) => r.email === recipientEmail);
if (!recipient?.signingUrl ) {
throw new Error ("Signing URL not available" );
}
return {
signingUrl : recipient.signingUrl ,
expiresAt : new Date (Date .now () + 24 * 60 * 60 * 1000 ),
};
}
Step 6: Input Validation import { z } from "zod" ;
const RecipientInputSchema = z.object ({
email : z
.string ()
.email ("Invalid email format" )
.transform ((e ) => e.toLowerCase ().trim ()),
name : z
.string ()
.min (1 , "Name is required" )
.max (100 , "Name too long" )
.transform ((n ) => n.trim ()),
role : z.enum (["SIGNER" , "APPROVER" , "VIEWER" , "CC" ]),
});
const DocumentInputSchema = z.object ({
title : z
.string ()
.min (1 , "Title is required" )
.max (255 , "Title too long" )
.refine (
(t ) => !/<script/i .test (t),
"Title contains invalid characters"
),
});
async function validatePdfUpload (
file : Buffer ,
maxSizeMb = 10
): Promise <boolean > {
const maxBytes = maxSizeMb * 1024 * 1024 ;
if (file.length > maxBytes) {
throw new Error (`File exceeds ${maxSizeMb} MB limit` );
}
const pdfMagic = Buffer .from ([0x25 , 0x50 , 0x44 , 0x46 ]);
if (!file.slice (0 , 4 ).equals (pdfMagic)) {
throw new Error ("File is not a valid PDF" );
}
return true ;
}
async function createDocumentHandler (input : unknown ) {
const validated = DocumentInputSchema .parse (input);
return client.documents .createV0 (validated);
}
Step 7: Audit Logging interface AuditEntry {
timestamp : Date ;
userId : string ;
action : string ;
resourceType : "document" | "template" | "recipient" ;
resourceId : string ;
ipAddress ?: string ;
userAgent ?: string ;
success : boolean ;
error ?: string ;
}
class AuditLogger {
private entries : AuditEntry [] = [];
log (entry : Omit <AuditEntry , "timestamp" >): void {
const fullEntry : AuditEntry = {
...entry,
timestamp : new Date (),
};
this .entries .push (fullEntry);
console .log (
`[AUDIT] ${entry.action} ${entry.resourceType} :${entry.resourceId} ` +
`by ${entry.userId} - ${entry.success ? "SUCCESS" : "FAILED" } `
);
if (!entry.success && entry.action === "delete" ) {
console .warn (`[SECURITY] Failed delete attempt by ${entry.userId} ` );
}
}
getRecentEntries (limit = 100 ): AuditEntry [] {
return this .entries .slice (-limit);
}
}
const auditLogger = new AuditLogger ();
async function auditedDeleteDocument (
userId : string ,
documentId : string
): Promise <boolean > {
try {
await client.documents .deleteV0 ({ documentId });
auditLogger.log ({
userId,
action : "delete" ,
resourceType : "document" ,
resourceId : documentId,
success : true ,
});
return true ;
} catch (error : any ) {
auditLogger.log ({
userId,
action : "delete" ,
resourceType : "document" ,
resourceId : documentId,
success : false ,
error : error.message ,
});
throw error;
}
}
Security Checklist
Output
Secure API key management
Validated webhook endpoints
Input sanitization
Audit trail for compliance
Error Handling Security Issue Indicator Response Invalid API key 401 errors Rotate key Webhook spoofing Invalid secret Reject and alert Unauthorized access 403 errors Check permissions Brute force Many 401s Rate limit IP
Resources
Next Steps For production deployment, see documenso-prod-checklist.