Grammarly Security Basics
DevOps & Securitygrammarly-security-basicsGrammarly Security Basics is an Agent Skill for devops & security published in the jeremylongshore/claude-code-plugins-plus-skills repository on GitHub: a SKILL.md instruction file an AI agent can load to do this task.
Security fundamentals for Grammarly API credential management. Use when setting up secure authentication and token handling for Grammarly integrations.
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/saas-packs/grammarly-pack/skills/grammarly-security-basics/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/grammarly-security-basics, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
Grammarly Security Basics
Overview
Grammarly processes user-written text content for grammar, tone, and style suggestions. Integrations handle document text that may contain confidential business communications, legal drafts, or personal correspondence. Security concerns include OAuth client credential management, ensuring user text is not persisted or logged unnecessarily, and protecting access tokens that grant read/write access to user documents and suggestion history.
API Key Management
function createGrammarlyClient(): { clientId: string; clientSecret: string } {
const clientId = process.env.GRAMMARLY_CLIENT_ID;
const clientSecret = process.env.GRAMMARLY_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new Error("Missing GRAMMARLY_CLIENT_ID or GRAMMARLY_CLIENT_SECRET");
}
// Access tokens from client_credentials grant — never persist to disk
console.log("Grammarly client initialized (client ID:", clientId.slice(0, 8), "...)");
return { clientId, clientSecret };
}
Webhook Signature Verification
import crypto from "crypto";
import { Request, Response, NextFunction } from "express";
function verifyGrammarlyWebhook(req: Request, res: Response, next: NextFunction): void {
const signature = req.headers["x-grammarly-signature"] as string;
const secret = process.env.GRAMMARLY_WEBHOOK_SECRET!;
const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
res.status(401).send("Invalid signature");
return;
}
next();
}
Input Validation
import { z } from "zod";
const TextAnalysisSchema = z.object({
document_id: z.string().uuid(),
text: z.string().min(1).max(100_000),
language: z.enum(["en-US", "en-GB", "en-AU", "en-CA"]).default("en-US"),
domain: z.enum(["general", "academic", "business", "casual"]).default("general"),
goals: z.array(z.enum(["clarity", "engagement", "delivery", "correctness"])).optional(),
});
function validateTextAnalysis(data: unknown) {
return TextAnalysisSchema.parse(data);
}
Data Protection
const GRAMMARLY_SENSITIVE_FIELDS = ["document_text", "user_email", "access_token", "client_secret", "suggestion_context"];
function redactGrammarlyLog(record: Record<string, unknown>): Record<string, unknown> {
const redacted = { ...record };
for (const field of GRAMMARLY_SENSITIVE_FIELDS) {
if (field in redacted) redacted[field] = "[REDACTED]";
}
// Truncate any text snippets to prevent content leakage
if (typeof redacted.text_preview === "string") {
redacted.text_preview = (redacted.text_preview as string).slice(0, 20) + "...";
}
return redacted;
}
Security Checklist
- Client secret stored in secrets vault, never in source code
- Access tokens held in memory only, never persisted to disk
- User document text never logged in application logs
- HTTPS enforced for all API calls
- Pre-commit hook blocks credential leaks (
grammarly_client_secret) - Token refresh logic handles expiration gracefully
- Text content encrypted at rest if cached locally
- OAuth scopes limited to minimum required permissions
Error Handling
| Vulnerability | Risk | Mitigation |
|---|---|---|
| Leaked client secret | Unauthorized document analysis access | Secrets vault + rotation |
| Access tokens persisted to disk | Token theft enables impersonation | In-memory only + short TTL |
| User text in application logs | Confidential content exposed | Field-level redaction pipeline |
| Overly broad OAuth scopes | Access to unrelated user documents | Minimum-privilege scope requests |
| Unencrypted text cache | Local storage breach exposes content | AES encryption for any local cache |
Resources
Next Steps
See grammarly-prod-checklist.