kq-config
v1.4.0
Published
Professional .kq config parser — server/client separation, AES-256 encryption, audit tools, CLI dashboard, 200-test suite, zero dependencies.
Downloads
260
Maintainers
Readme
kq-config
A professional .kq config file parser for Node.js — server/client block separation, built-in .env support, AES-256-GCM encryption, audit tools, CLI dashboard, schema validation, and zero external dependencies.
What does kq mean?
kq stands for Konfig Query — and is also formed from the first and last letters of the author's name, Kanishq.
The .kq extension is a custom config format built from the ground up — purpose-built for projects that need clean separation between server and client configuration, with professional-grade security baked in.
Why kq-config?
Most config solutions give everyone access to everything. kq-config lets you define server and client blocks in a single file — your server only reads server values, your client only reads client values. Secrets never leak to the frontend.
config.kq
├── ::shared → merged into both server and client
├── ::server → server only (db passwords, jwt secrets, ports)
└── ::client → client only (api urls, themes, timeouts)Features
- Block separation —
::serverand::clientin one file; each side reads only its own - Shared values —
::sharedblock merged into both automatically - Built-in
.envsupport — nodotenvneeded; auto-loads.envfrom config folder $ENV:injection —db_pass = $ENV:DB_PASS— secrets never hardcoded- AES-256-GCM encryption —
ENC:syntax for encrypted values in config files - Secret masking —
.all(true)hides sensitive values in output/logs - Raw secret detection — warns when plain secrets found in config
- Layered overrides — base + environment-specific override (dev → staging → prod)
- Runtime overrides —
KQ_SERVER_PORT=9999beats everything - Schema validation — required, type, min/max, pattern, enum, defaults
- Auto type casting —
"3000"→3000,"true"→true - Audit methods — unused, missing, active, invalid env vars
- Diff support — compare two configs, see what changed
- Snapshot — point-in-time view of config
- CLI tool —
npx kq inspectopens browser dashboard - Pre-commit hook — blocks commits containing raw secrets
- Example file generator —
npx kq examplegenerates.kq.examplefrom your config (safe to commit) - Gitignore safety check —
npx kq gitignoreverifies sensitive files are in.gitignore - TypeScript support — full type definitions included
- ESM + CJS — works with both
importandrequire - Zero dependencies — nothing to install, nothing to audit
Install
npm install kq-configQuick Start
config.kq:
::shared
app_name = MyApp
version = 1.0
::end
::server
host = localhost
port = 3000
db_pass = $ENV:DB_PASS
secret_key = $ENV:SECRET_KEY
debug = true
::end
::client
api_url = http://localhost:3000
theme = dark
timeout = 5000
::end.env:
DB_PASS=supersecret
SECRET_KEY=myjwtsecretYour code:
const { KQParser } = require("kq-config");
const path = require("path");
const server = new KQParser(path.join(__dirname, "config.kq"), "server").load();
console.log(server.get("port")); // 3000
console.log(server.get("db_pass")); // "supersecret" — from .env automatically
const client = new KQParser(path.join(__dirname, "config.kq"), "client").load();
console.log(client.get("api_url")); // "http://localhost:3000"
console.log(client.get("db_pass")); // undefined — client can NEVER see this ✅Import Styles
// CommonJS
const { KQParser } = require("kq-config");
// ES Module
import { KQParser } from "kq-config";
// TypeScript
import { KQParser, KQSchema, KQOptions, KQAuditResult } from "kq-config";Recommended Project Setup
your-project/
├── config.kq ← base config (commit ✅)
├── config.prod.kq ← production overrides (DO NOT commit ❌)
├── config.prod.kq.example ← prod template (commit ✅)
├── .env ← your secrets (DO NOT commit ❌)
├── .env.example ← secrets template (commit ✅)
└── server.js.gitignore:
.env
config.prod.kq
config.staging.kqBuilt-in .env Support
No need to install dotenv. kq-config auto-loads .env from the same folder as your config.kq:
// .env loaded automatically — nothing extra needed
const server = new KQParser("config.kq", "server").load();Custom path:
new KQParser("config.kq", "server", null, { envFile: ".env.production" });Disable:
new KQParser("config.kq", "server", null, { envFile: false });Shell always wins over .env file:
.env file ← lower priority
shell env var ← wins if same key set in shellEnvironment Overrides
config.prod.kq — only what changes in production:
::server
host = 0.0.0.0
port = 8080
debug = false
log_level = warn
::end
::client
api_url = https://api.example.com
::endLoad based on APP_ENV:
const env = process.env.APP_ENV || "development";
const overrides = {
production: "config.prod.kq",
staging: "config.staging.kq",
};
const overrideFile = overrides[env]
? path.join(__dirname, overrides[env])
: null;
const server = new KQParser("config.kq", "server", overrideFile).load();Run commands:
node server.js # development
APP_ENV=staging node server.js # staging
APP_ENV=production node server.js # production
# Windows CMD
set APP_ENV=production && node server.js
# Windows PowerShell
$env:APP_ENV="production"; node server.jsSchema Validation
const server = new KQParser("config.kq", "server").load().validate({
host: { type: "string", required: true },
port: { type: "number", required: true, min: 1, max: 65535 },
secret_key: { type: "string", required: true },
log_level: {
type: "string",
required: false,
default: "info",
enum: ["debug", "info", "warn", "error"],
},
debug: { type: "boolean", required: false, default: false },
});Throws KQValidationError listing all errors at once:
KQValidationError: Config validation failed for role 'server':
✗ Required key 'secret_key' is missing
✗ 'port' — value 99999 is above maximum 65535Runtime Overrides
KQ_SERVER_PORT=9999 node server.js
KQ_CLIENT_THEME=light node server.js
KQ_SERVER_DEBUG=false node server.jsPattern: KQ_<ROLE>_<KEY>=value — values are automatically type-cast.
Override Priority
.env file
↓
config.kq (::shared)
↓
config.kq (::server or ::client)
↓
config.prod.kq (override file)
↓
shell environment variables
↓
KQ_SERVER_PORT=9999 ← always winsSecurity Features
1. AES-256-GCM Encryption (ENC: syntax)
Store encrypted secrets directly in your .kq file:
Generate a master key:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# 3a7bd3e2360a3d29eea436fcfb7e44c735d117c7888a8660b1e5c8c51b9ff59fAdd to .env:
KQ_MASTER_KEY=3a7bd3e2360a3d29eea436fcfb7e44c735d117c7888a8660b1e5c8c51b9ff59fEncrypt a value:
process.env.KQ_MASTER_KEY = "3a7bd3e2...";
const enc = KQParser.encrypt("mysupersecretpassword");
// "ENC:aGVsbG8gd29ybGQ=:randomIV:authTag"
// Or generate a fresh key
const key = KQParser.generateKey(); // 64-char hex stringPut it in config.kq:
::server
db_pass = ENC:aGVsbG8gd29ybGQ=:randomIV:authTag
secret_key = ENC:dGhpcyBpcyBhIHNlY3JldA==:iv2:tag2
::endLoad — decryption is automatic:
const server = new KQParser("config.kq", "server").load();
server.get("db_pass"); // "mysupersecretpassword" ✅Decrypt manually:
const dec = KQParser.decrypt("ENC:aGVsbG8gd29ybGQ=:randomIV:authTag");2. Secret masking
server.all(); // { port: 3000, db_pass: "supersecret" }
server.all(true); // { port: 3000, db_pass: "***MASKED***" } ✅
// Always mask
new KQParser("config.kq", "server", null, { mask: true });Keys automatically masked: anything matching password, secret, token, api_key, jwt, auth, credential, signing, cipher, salt, hmac.
3. Raw secret detection
# config.kq — WARNING triggered
::server
db_pass = ghp_abc123XYZ789realtoken ← GitHub token detected
::end
# Warning: Key 'db_pass' looks like a secret with a raw value.
# Use '$ENV:DB_PASS' or 'ENC:' instead.4. All security protections
| Protection | What it blocks |
| ------------------- | ----------------------------------------------------- |
| Path traversal | ../../etc/passwd attacks |
| Prototype pollution | __proto__, constructor, then etc. |
| Nested blocks | ::server inside ::server |
| Key format | Only [a-z_][a-z0-9_]* — hyphens/dots/spaces blocked |
| ReDoS | Values over 10,000 characters |
| Memory exhaustion | Files over 1MB |
| Key length | Keys over 256 characters |
| Null bytes | Null bytes in values |
| Control characters | Control characters in values |
| Env hijacking | .env cannot overwrite NODE_OPTIONS, PATH etc. |
| Circular override | Base and override cannot be same file |
| Symlink attack | Symlinks outside cwd blocked |
| Supply chain | Zero external dependencies |
| npm provenance | Every release cryptographically signed |
5. $ENV: vs ENC: — when to use which
| | $ENV:VAR | ENC:ciphertext |
| ----------------- | -------------------- | ------------------------ |
| Secret lives in | .env file or shell | inside config.kq |
| Can commit to git | no | yes — encrypted |
| Needs master key | no | yes — KQ_MASTER_KEY |
| Best for | local dev, CI/CD | committing config safely |
Audit Methods
const server = new KQParser("config.kq", "server").load();
// All $ENV: vars that are set and working
server.activeEnv();
// [{ key: "DB_PASS", source: ".env file" }, { key: "SECRET_KEY", source: "shell environment" }]
// $ENV: vars referenced in config but not set anywhere
server.missingEnv();
// [{ key: "OLD_VAR", suggestion: "Add 'OLD_VAR=your_value' to your .env file" }]
// .env vars never referenced in any config file
server.unusedEnv();
// [{ key: "FORGOTTEN", suggestion: "Remove from .env — not referenced..." }]
// .env vars with empty or placeholder values
server.invalidEnv();
// [{ key: "API_KEY", value: "***", reason: "Looks like a placeholder" }]
// All four at once
server.audit();
// { active: [...], missing: [...], unused: [...], invalid: [...] }
// Warn automatically at load time
new KQParser("config.kq", "server", null, { warnUnused: true }).load();Diff & Snapshot
const dev = new KQParser("config.kq", "server").load();
const prod = new KQParser("config.kq", "server", "config.prod.kq").load();
// Compare two configs
const diff = dev.diff(prod);
// {
// added: {},
// removed: {},
// changed: { port: { from: 3000, to: 8080 }, debug: { from: true, to: false } }
// }
// Point-in-time snapshot
const snap = server.snapshot();
// { role: "server", filepath: "...", loadedAt: "...", config: {...masked}, keyCount: 14 }
// Reload config (re-reads files)
server.reload();CLI Tool
# Generate .kq.example file — safe to commit to GitHub
npx kq example
# Custom output path
npx kq example --file config.kq --output config.kq.example
# Check .gitignore covers all sensitive files
npx kq gitignore
# Open browser dashboard at http://localhost:3737
npx kq inspect
# Custom options
npx kq inspect --role server --port 4000
npx kq inspect --file config.kq --override config.prod.kq
# Terminal audit report
npx kq audit
# Encrypt / decrypt
npx kq encrypt "mysecret"
npx kq decrypt "ENC:..."Generate Example File
generateExample() reads your .kq file and generates a .kq.example file where:
$ENV:DB_PASS→YOUR_DB_PASS_HEREENC:abc:iv:tag→YOUR_ENCRYPTED_SECRET_KEY_HERE- Plain values like
port = 3000→ unchanged
The output file is safe to commit to GitHub. Share it with your team so they know what values to set.
# From CLI — generates config.kq.example automatically
npx kq example
# Custom output path
npx kq example --output config.kq.example// From code
const result = KQParser.generateExample("config.kq");
// result.path → "config.kq.example"
// result.replacements → [{ key: "db_pass", type: "env", was: "$ENV:DB_PASS" }, ...]Example input (config.kq):
::server
port = 3000
db_pass = $ENV:DB_PASS
secret_key = ENC:abc:iv:tag
::endGenerated output (config.kq.example):
# config.kq.example — safe to commit to GitHub
# Copy this file to config.kq and fill in the values.
::server
port = 3000
db_pass = YOUR_DB_PASS_HERE
secret_key = YOUR_ENCRYPTED_SECRET_KEY_HERE
::endGitignore Safety Check
npx kq gitignore checks that all sensitive files are listed in .gitignore. Run it any time to verify you are not at risk of accidentally committing secrets.
npx kq gitignoreIf files are missing:
MISSING .env -- NOT in .gitignore (contains secrets)
Fix: echo ".env" >> .gitignore
MISSING config.prod.kq -- NOT in .gitignore (production config)
Fix: echo "config.prod.kq" >> .gitignore
WARNING: 2 file(s) not in .gitignore -- risk of commit!If all covered:
OK .env (contains secrets)
OK config.prod.kq (production config)
All 2 sensitive file(s) are in .gitignore -- safe!kq-config also automatically emits a KQGitignoreWarning at .load() time if sensitive files are not in .gitignore — so you get warned even if you forget to run the command.
Pre-commit Hook
Blocks commits containing raw secrets:
# Install once
node scripts/pre-commit.js --install
# Blocks: .env files, config.prod.kq, AWS keys, GitHub tokens,
# Stripe keys, Slack tokens, JWTs, raw passwords in .kq files.kq File Syntax
# comment → ignored entirely
@version = 1.0 → meta directive (ignored by parser)
::shared → open shared block
app_name = MyApp
::end → close block
::server
port = 3000 integer — auto cast
debug = true boolean — auto cast
score = 9.5 float — auto cast
nullable = null null — auto cast
greeting = "hello world" quoted string
db_pass = $ENV:DB_PASS from environment
secret_key = ENC:abc:iv:tag encrypted value
timeout = 5000 # ms inline comment
::endAuto Type Casting
| File value | Parsed as |
| ---------------- | --------------------------------- |
| 3000 | 3000 (number) |
| 3.14 | 3.14 (float) |
| true / false | boolean |
| null | null |
| "hello world" | "hello world" (quotes stripped) |
| anything else | string |
Key Rules
- Keys must match
[a-z_][a-z0-9_]*— only lowercase letters, numbers, underscores - Uppercase keys are automatically lowercased (
MyPort→myport) - Hyphens, dots, spaces in keys are not allowed and throw
KQParseError
Full API Reference
new KQParser(filepath, role, overrideFile?, options?)
| Parameter | Type | Description |
| -------------------- | --------------- | ----------------------------------------- |
| filepath | string | Path to your .kq file |
| role | string | "server", "client", or any block name |
| overrideFile | string\|null | Optional override file |
| options.envFile | string\|false | Custom .env path or false to disable |
| options.mask | boolean | Always mask secrets in .all() |
| options.warnUnused | boolean | Warn at load for unused .env vars |
Methods
| Method | Returns | Description |
| -------------------------------------- | ---------- | -------------------------------------- |
| .load() | this | Load and merge all layers |
| .reload() | this | Reload config again |
| .validate(schema) | this | Validate against schema |
| .get(key, fallback?) | value | Get single value |
| .has(key) | boolean | Check if key exists |
| .all(mask?) | object | All values (masked if true) |
| .keys() | string[] | All keys |
| .values() | array | All values |
| .entries() | array | All [key, value] pairs |
| .size() | number | Number of keys |
| .snapshot() | object | Point-in-time config view |
| .diff(other) | object | Compare two parsers |
| .unusedEnv() | array | Unused .env vars |
| .missingEnv() | array | Missing env vars |
| .activeEnv() | array | Active env vars |
| .invalidEnv() | array | Invalid .env values |
| .audit() | object | All four audit categories |
| KQParser.generateExample(file, out?) | object | Generate .kq.example with placeholders |
| KQParser.encrypt(text) | string | Encrypt with AES-256-GCM |
| KQParser.decrypt(enc) | string | Decrypt ENC: value |
| KQParser.generateKey() | string | Generate 64-char hex key |
Error Types
| Error | When thrown | Extra properties |
| --------------------- | --------------------------------- | ----------------------- |
| KQError | Base class | — |
| KQFileNotFoundError | Config file not found | .filepath |
| KQEnvError | $ENV:VAR not set anywhere | .varName |
| KQValidationError | Schema validation fails | — |
| KQParseError | Invalid syntax or blocked content | .filepath, .lineNum |
import {
KQParser,
KQFileNotFoundError,
KQEnvError,
KQValidationError,
KQParseError,
} from "kq-config";
try {
const server = new KQParser("config.kq", "server").load().validate(schema);
} catch (e) {
if (e.name === "KQFileNotFoundError")
console.error("File not found:", e.filepath);
else if (e.name === "KQEnvError")
console.error("Missing env var:", e.varName);
else if (e.name === "KQParseError")
console.error(`Line ${e.lineNum}:`, e.message);
else if (e.name === "KQValidationError") console.error(e.message);
process.exit(1);
}Reporting a Vulnerability
Please do not open a public GitHub issue for security vulnerabilities. See SECURITY.md for how to report privately.
License
MIT — © 2026 kanishq-9
