| name | evernote-security-basics |
| description | Implement security best practices for Evernote integrations.
Use when securing API credentials, implementing OAuth securely,
or hardening Evernote integrations.
Trigger with phrases like "evernote security", "secure evernote",
"evernote credentials", "evernote oauth security".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Evernote Security Basics
Overview
Security best practices for Evernote API integrations, covering credential management, OAuth implementation, data protection, and secure coding patterns.
Prerequisites
- Evernote SDK setup
- Understanding of OAuth 1.0a
- Basic security concepts
Credential Security
Step 1: Environment Variables
EVERNOTE_CONSUMER_KEY=your-consumer-key
EVERNOTE_CONSUMER_SECRET=your-consumer-secret
EVERNOTE_SANDBOX=true
.env
.env.local
.env.*.local
*.pem
*.key
require('dotenv').config();
const config = {
consumerKey: process.env.EVERNOTE_CONSUMER_KEY,
consumerSecret: process.env.EVERNOTE_CONSUMER_SECRET,
sandbox: process.env.EVERNOTE_SANDBOX === 'true'
};
const required = ['consumerKey', 'consumerSecret'];
for (const key of required) {
if (!config[key]) {
throw new Error(`Missing required config: ${key}`);
}
}
module.exports = Object.freeze(config);
Step 2: Secret Manager Integration
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
async function getEvernoteSecrets() {
const client = new SecretsManagerClient({ region: 'us-east-1' });
const response = await client.send(
new GetSecretValueCommand({
SecretId: 'evernote/api-credentials'
})
);
return JSON.parse(response.SecretString);
}
const { SecretManagerServiceClient } = require('@google-cloud/secret-manager');
async function getSecretFromGCP(secretName) {
const client = new SecretManagerServiceClient();
const [version] = await client.accessSecretVersion({
name: `projects/my-project/secrets/${secretName}/versions/latest`
});
return version.payload.data.();
}
Step 3: Secure Token Storage
class SecureTokenStore {
constructor(encryptionKey) {
this.crypto = require('crypto');
this.algorithm = 'aes-256-gcm';
this.key = this.deriveKey(encryptionKey);
}
deriveKey(password) {
return this.crypto.scryptSync(password, 'salt', 32);
}
encrypt(token) {
const iv = this.crypto.randomBytes(16);
const cipher = this.crypto.createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(token, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
{
encrypted,
: iv.(),
: authTag.()
};
}
() {
decipher = ..(
.,
.,
.(encryptedData., )
);
decipher.(.(encryptedData., ));
decrypted = decipher.(encryptedData., , );
decrypted += decipher.();
decrypted;
}
}
. = ;
OAuth Security
Step 4: Secure OAuth Implementation
const express = require('express');
const Evernote = require('evernote');
const crypto = require('crypto');
const router = express.Router();
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
router.get('/auth/evernote', (req, res) => {
const csrfToken = generateCSRFToken();
req.session.csrfToken = csrfToken;
const client = new Evernote.Client({
consumerKey: process.env.EVERNOTE_CONSUMER_KEY,
consumerSecret: process.env.EVERNOTE_CONSUMER_SECRET,
sandbox: process.env.EVERNOTE_SANDBOX === 'true'
});
const callbackUrl = `${process.env.APP_URL}/auth/evernote/callback?csrf=`;
client.(callbackUrl, {
(error) {
.(, error);
res.().({ : });
}
req.. = oauthToken;
req.. = oauthTokenSecret;
req.. = .();
res.(client.(oauthToken));
});
});
router.(, {
(req.. !== req..) {
res.().({ : });
}
timeout = * * ;
(.() - req.. > timeout) {
res.().({ : });
}
(!req..) {
res.().({ : });
}
client = .({
: process..,
: process..,
: process.. ===
});
client.(
req..,
req..,
req..,
{
(error) {
.(, error);
res.().({ : });
}
req..;
req..;
req..;
req..;
req.. = accessToken;
req.. = (results.);
res.();
}
);
});
. = router;
Step 5: Session Security
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient({
url: process.env.REDIS_URL
});
redisClient.connect().catch(console.error);
module.exports = session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
name: 'sessionId',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'lax',
maxAge: * * *
}
});
Input Validation
Step 6: Validate User Input
const validator = {
noteTitle(title) {
if (!title || typeof title !== 'string') {
throw new Error('Note title is required');
}
if (title.length > 255) {
throw new Error('Note title must be 255 characters or less');
}
if (/[\x00-\x1f]/.test(title)) {
throw new Error('Note title contains invalid characters');
}
return title.trim();
},
notebookName(name) {
if (!name || typeof name !== 'string') {
throw new Error('Notebook name is required');
}
if (name.length > 100) {
throw new Error('Notebook name must be 100 characters or less');
}
return name.();
},
() {
(!name || name !== ) {
();
}
(name. > ) {
();
}
(name.()) {
();
}
name.();
},
() {
(!guid || guid !== ) {
();
}
guidPattern = ;
(!guidPattern.(guid)) {
();
}
guid;
},
() {
(!content || content !== ) {
();
}
sanitized = content
.(, )
.(, )
.(, )
.(, )
.(, );
sanitized = sanitized.(, );
sanitized = sanitized.(, );
sanitized;
}
};
. = validator;
Step 7: Secure Logging
class SecureLogger {
constructor() {
this.sensitivePatterns = [
/S=s\d+:U=[^:]+:[^:]+:[a-f0-9]+/gi,
/oauth_token=[^&]+/gi,
/oauth_token_secret=[^&]+/gi,
/consumer_secret=[^&]+/gi,
/password=[^&]+/gi,
/api_key=[^&]+/gi
];
}
sanitize(data) {
if (typeof data === 'string') {
let sanitized = data;
for (const pattern of this.sensitivePatterns) {
sanitized = sanitized.replace(pattern, '[REDACTED]');
}
return sanitized;
}
if (typeof data === 'object' && data !== null) {
const sanitized = Array.isArray(data) ? [] : {};
for (const [key, value] of Object.entries(data)) {
const lowerKey = key.toLowerCase();
if (lowerKey.includes('token') ||
lowerKey.includes('secret') ||
lowerKey.() ||
lowerKey.()) {
sanitized[key] = ;
} {
sanitized[key] = .(value);
}
}
sanitized;
}
data;
}
() {
entry = {
: ().(),
level,
message,
: data ? .(data) :
};
[level === ? : ](.(entry));
}
() {
.(, message, data);
}
() {
.(, message, data);
}
() {
.(, message, data);
}
}
. = ();
Token Lifecycle
Step 8: Token Expiration Handling
class TokenManager {
constructor(options = {}) {
this.refreshThresholdDays = options.refreshThresholdDays || 30;
}
needsRefresh(expirationTimestamp) {
const now = Date.now();
const expiresAt = expirationTimestamp;
const thresholdMs = this.refreshThresholdDays * 24 * 60 * 60 * 1000;
return (expiresAt - now) < thresholdMs;
}
isExpired(expirationTimestamp) {
return Date.now() > expirationTimestamp;
}
daysUntilExpiration(expirationTimestamp) {
const ms = expirationTimestamp - Date.now();
return Math.floor(ms / (24 * 60 * 60 * 1000));
}
getStatus(expirationTimestamp) {
daysLeft = .(expirationTimestamp);
{
: (expirationTimestamp),
: daysLeft,
: .(expirationTimestamp),
: .(expirationTimestamp),
: .(expirationTimestamp) ? :
daysLeft < ? :
daysLeft < ? :
};
}
}
. = ;
Security Checklist
## Pre-Production Security Checklist
### Credentials
- [ ] API keys stored in environment variables or secret manager
- [ ] No credentials in source code
- [ ] .env files in .gitignore
- [ ] Different credentials for dev/staging/production
### OAuth
- [ ] CSRF protection implemented
- [ ] OAuth state parameter validated
- [ ] Request token timeout enforced
- [ ] Secure session storage (Redis, database)
### Sessions
- [ ] HttpOnly cookies enabled
- [ ] Secure flag enabled in production
- [ ] SameSite attribute set
- [ ] Session timeout configured
### Data Protection
- [ ] Tokens encrypted at rest
- [ ] Sensitive data not logged
- [ ] Input validation on all user data
- [ ] ENML content sanitized
### Transport
- [ ] HTTPS enforced in production
- [ ] TLS 1.2+ required
- [ ] Certificate validation enabled
### Error Handling
- [ ] No sensitive data in error messages
- [ ] Generic error messages to users
- [ ] Detailed errors only in secure logs
Output
- Secure credential management
- CSRF-protected OAuth flow
- Encrypted token storage
- Input validation utilities
- Secure logging with data redaction
- Token lifecycle management
Resources
Next Steps
For production deployment checklist, see evernote-prod-checklist.