Skip to main content 首页 创作者 jeremylongshore tons-of-skills-marketplace linear-security-basics
linear-security-basics Secure API key management, OAuth best practices, and webhook
verification for Linear integrations.
Trigger: "linear security", "linear API key security",
"linear OAuth", "secure linear", "linear webhook verification",
"linear secrets management", "linear token refresh".
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/jeremylongshore/tons-of-skills-marketplace --skill linear-security-basics命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... 同仓库更多 Skills langchain-deploy-integration Deploy a LangChain 1.0 / LangGraph 1.0 app to Cloud Run, Vercel, or LangServe correctly — with timeouts sized for chain length, cold-start mitigation, SSE anti-buffering headers, and Secret Manager over .env. Use when prepping a first production deploy, debugging a stream that hangs behind a proxy, or diagnosing p99 latency spikes. Trigger with "langchain deploy", "langchain cloud run", "langchain vercel python", "langchain langserve", or "langchain docker".
langchain-langgraph-agents Build a correct LangGraph 1.0 ReAct agent with create_react_agent — typed tools, error propagation, recursion caps, and stop conditions that actually stop. Use when writing a first tool-calling agent, migrating from AgentExecutor or initialize_agent, or diagnosing an agent that loops on vague prompts. Trigger with "langgraph agent", "create_react_agent", "langgraph tool calling", "AgentExecutor migration", or "agent loop cost".
langchain-langgraph-human-in-loop Build LangGraph 1.0 human-in-the-loop approval flows with interrupt_before /
interrupt_after and Command(resume=...) — JSON-serializable state, clean
resume semantics, and UI wiring for approval decisions. Use when adding an
approval gate before an expensive tool call, wiring a Slack/web UI for agent
approvals, or debugging a graph that crashes on interrupt.
Trigger with "langgraph human in loop", "langgraph interrupt_before",
"langgraph approval flow", "Command resume", "langgraph HITL".
jeremylongshore
jeremylongshore/tons-of-skills-marketplace
打开 GitHub 仓库 name linear-security-basics description Secure API key management, OAuth best practices, and webhook
verification for Linear integrations.
Trigger: "linear security", "linear API key security",
"linear OAuth", "secure linear", "linear webhook verification",
"linear secrets management", "linear token refresh".
allowed-tools Read, Write, Edit, Grep version 1.12.0 license MIT author Jeremy Longshore <jeremy@intentsolutions.io> tags ["saas","linear","api","security","authentication"] compatibility Designed for Claude Code
Linear Security Basics
Overview
Secure authentication patterns for Linear integrations: API key management, OAuth 2.0 with PKCE, token refresh (mandatory for new apps after Oct 2025), webhook HMAC-SHA256 signature verification, and secret rotation.
Prerequisites
Linear account with API access
Understanding of environment variables and secret management
Familiarity with OAuth 2.0 and HMAC concepts
Instructions
Step 1: Secure API Key Storage
import { LinearClient } from "@linear/sdk" ;
const client = new LinearClient ({
apiKey : process.env .LINEAR_API_KEY !,
});
Environment setup:
LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
LINEAR_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx
.env
.env .*
!.env.example
LINEAR_API_KEY=lin_api_your_key_here
LINEAR_WEBHOOK_SECRET=your_webhook_secret_here
Startup validation:
function validateConfig ( ): void {
const key = process.env .LINEAR_API_KEY ;
if (!key) throw new Error ("LINEAR_API_KEY is required" );
if (!key.startsWith ( )) ( );
(key. < ) ( );
}
();
"lin_api_"
throw
new
Error
"LINEAR_API_KEY has invalid format"
if
length
30
throw
new
Error
"LINEAR_API_KEY appears truncated"
validateConfig
Step 2: OAuth 2.0 with PKCE import express from "express" ;
import crypto from "crypto" ;
const app = express ();
const OAUTH = {
clientId : process.env .LINEAR_CLIENT_ID !,
clientSecret : process.env .LINEAR_CLIENT_SECRET !,
redirectUri : process.env .LINEAR_REDIRECT_URI !,
scopes : ["read" , "write" , "issues:create" ],
};
function generatePKCE ( ) {
const verifier = crypto.randomBytes (32 ).toString ("base64url" );
const challenge = crypto.createHash ("sha256" ).update (verifier).digest ("base64url" );
return { verifier, challenge };
}
app.get ("/auth/linear" , (req, res ) => {
const state = crypto.randomBytes (16 ).toString ("hex" );
const { verifier, challenge } = generatePKCE ();
req.session !.oauthState = state;
req.session !.codeVerifier = verifier;
const url = new URL ("https://linear.app/oauth/authorize" );
url.searchParams .set ("client_id" , OAUTH .clientId );
url.searchParams .set ("redirect_uri" , OAUTH .redirectUri );
url.searchParams .set ("response_type" , "code" );
url.searchParams .set ("scope" , OAUTH .scopes .join ("," ));
url.searchParams .set ("state" , state);
url.searchParams .set ("code_challenge" , challenge);
url.searchParams .set ("code_challenge_method" , "S256" );
res.redirect (url.toString ());
});
app.get ("/auth/linear/callback" , async (req, res) => {
const { code, state } = req.query ;
if (state !== req.session !.oauthState ) {
return res.status (400 ).json ({ error : "Invalid state parameter" });
}
const response = await fetch ("https://api.linear.app/oauth/token" , {
method : "POST" ,
headers : { "Content-Type" : "application/x-www-form-urlencoded" },
body : new URLSearchParams ({
grant_type : "authorization_code" ,
code : code as string ,
client_id : OAUTH .clientId ,
client_secret : OAUTH .clientSecret ,
redirect_uri : OAUTH .redirectUri ,
code_verifier : req.session !.codeVerifier ,
}),
});
const tokens = await response.json ();
await storeTokens (req.user !.id , {
accessToken : encrypt (tokens.access_token ),
refreshToken : encrypt (tokens.refresh_token ),
expiresAt : new Date (Date .now () + tokens.expires_in * 1000 ),
});
res.redirect ("/dashboard" );
});
Step 3: Token Refresh As of Oct 2025, all new Linear OAuth apps issue refresh tokens. Existing apps must migrate by April 2026.
async function getValidToken (userId : string ): Promise <string > {
const stored = await getStoredTokens (userId);
if (stored.expiresAt .getTime () - Date .now () < 5 * 60 * 1000 ) {
const response = await fetch ("https://api.linear.app/oauth/token" , {
method : "POST" ,
headers : { "Content-Type" : "application/x-www-form-urlencoded" },
body : new URLSearchParams ({
grant_type : "refresh_token" ,
refresh_token : decrypt (stored.refreshToken ),
client_id : process.env .LINEAR_CLIENT_ID !,
client_secret : process.env .LINEAR_CLIENT_SECRET !,
}),
});
if (!response.ok ) throw new Error (`Token refresh failed: ${response.status} ` );
const tokens = await response.json ();
await storeTokens (userId, {
accessToken : encrypt (tokens.access_token ),
refreshToken : encrypt (tokens.refresh_token ),
expiresAt : new Date (Date .now () + tokens.expires_in * 1000 ),
});
return tokens.access_token ;
}
return decrypt (stored.accessToken );
}
Step 4: Webhook Signature Verification Linear signs every webhook with HMAC-SHA256 using the webhook's signing secret. The signature is in the Linear-Signature header.
import crypto from "crypto" ;
function verifyWebhookSignature (
rawBody : string ,
signature : string ,
secret : string
): boolean {
const expected = crypto
.createHmac ("sha256" , secret)
.update (rawBody)
.digest ("hex" );
try {
return crypto.timingSafeEqual (
Buffer .from (signature),
Buffer .from (expected)
);
} catch {
return false ;
}
}
app.post ("/webhooks/linear" , express.raw ({ type : "*/*" }), (req, res ) => {
const signature = req.headers ["linear-signature" ] as string ;
const rawBody = req.body .toString ();
if (!verifyWebhookSignature (rawBody, signature, process.env .LINEAR_WEBHOOK_SECRET !)) {
return res.status (401 ).json ({ error : "Invalid signature" });
}
const event = JSON .parse (rawBody);
const age = Date .now () - event.webhookTimestamp ;
if (age > 60000 ) {
return res.status (400 ).json ({ error : "Webhook too old" });
}
processEvent (event).catch (console .error );
res.json ({ received : true });
});
Step 5: Secret Rotation
const apiKeys = [
process.env .LINEAR_API_KEY_NEW ,
process.env .LINEAR_API_KEY_OLD ,
].filter (Boolean ) as string [];
async function getWorkingClient ( ): Promise <LinearClient > {
for (const apiKey of apiKeys) {
try {
const client = new LinearClient ({ apiKey });
await client.viewer ;
return client;
} catch {
continue ;
}
}
throw new Error ("No valid Linear API key found" );
}
Security Checklist
Error Handling Error Cause Solution Invalid signatureWebhook secret mismatch Verify LINEAR_WEBHOOK_SECRET in Linear Settings > API > Webhooks invalid_grantRefresh token expired/revoked Re-initiate full OAuth flow Invalid scopeApp not authorized for scope Request only scopes your app needs Authentication requiredToken expired, refresh failed Trigger re-authentication
Examples
Test Webhook Signature Locally import crypto from "crypto" ;
const secret = "test-signing-secret" ;
const payload = JSON .stringify ({
action : "create" ,
type : "Issue" ,
data : { id : "test" , title : "Test" },
webhookTimestamp : Date .now (),
});
const sig = crypto.createHmac ("sha256" , secret).update (payload).digest ("hex" );
console .log (`Signature: ${sig} ` );
console .log (`Valid: ${verifyWebhookSignature(payload, sig, secret)} ` );
Resources