@shieldly/cli 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/dist/cli.cjs +47 -44
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,8 +20,8 @@ shieldly analyze-iam policy.json
20
20
  shieldly analyze-cf template.yaml
21
21
  ```
22
22
 
23
- For higher limits and analysis history,
24
- [create a free account](https://www.shieldly.io/app/api) and set your key:
23
+ For higher limits and analysis history, create an account
24
+ ([API keys require a Builder plan or above](https://www.shieldly.io/app/api)) and set your key:
25
25
 
26
26
  ```bash
27
27
  export SHIELDLY_API_KEY=sk_live_...
@@ -101,6 +101,14 @@ hashes of the input.
101
101
  - API reference: https://www.shieldly.io/docs/api
102
102
  - Issues: https://github.com/shieldly-io/cli/issues
103
103
 
104
+ ## Free tools & references (no signup)
105
+
106
+ No account required — these run in your browser or document the risks:
107
+
108
+ - [IAM Privilege Escalation Cheat Sheet](https://www.shieldly.io/iam/cheatsheet?utm_source=github&utm_medium=readme) — every common escalation path on one page, with fixes
109
+ - [Free browser tools](https://www.shieldly.io/tools?utm_source=github&utm_medium=readme) — IAM policy linter, trust policy explainer, S3 bucket policy checker, CloudFormation IAM checker
110
+ - [IAM privilege escalation reference](https://www.shieldly.io/iam?utm_source=github&utm_medium=readme) — each method with a vulnerable policy, the exploit, and the fix
111
+
104
112
  ## License
105
113
 
106
114
  MIT © Shieldly
package/dist/cli.cjs CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- var ne=Object.create;var G=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var le=Object.getPrototypeOf,ae=Object.prototype.hasOwnProperty;var ce=(e,s,o,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of ie(s))!ae.call(e,t)&&t!==o&&G(e,t,{get:()=>s[t],enumerable:!(n=re(s,t))||n.enumerable});return e};var W=(e,s,o)=>(o=e!=null?ne(le(e)):{},ce(s||!e||!e.__esModule?G(o,"default",{value:e,enumerable:!0}):o,e));var k=require("node:fs"),Y=require("node:path");var $=require("node:fs"),B=require("node:os"),z=require("node:path"),O="Shieldly-CLI/1.0.3",U=(0,z.join)((0,B.homedir)(),".shieldly","config.json"),pe="https://api.shieldly.io",ye="https://www.shieldly.io",H=5;function K(){try{return JSON.parse((0,$.readFileSync)(U,"utf8"))}catch{return{}}}function de(e){try{(0,$.mkdirSync)((0,z.dirname)(U),{recursive:!0}),(0,$.writeFileSync)(U,`${JSON.stringify(e,null,2)}
3
- `)}catch{}}function S(e){return e||(process.env.SHIELDLY_API_KEY?process.env.SHIELDLY_API_KEY:K().apiKey||null)}function q(){let e=K().demoCount;return typeof e=="number"&&e>0?e:0}function _(){return q()>=H}function j(){let e=K();return e.demoCount=q()+1,de(e),Math.max(0,H-e.demoCount)}function D(){return`You've used all ${H} free demo analyses.
4
-
5
- Get a free API key for higher limits: https://www.shieldly.io/app/api
6
-
7
- Then set SHIELDLY_API_KEY or pass --api-key.`}function N(){return(process.env.SHIELDLY_API_URL||pe).replace(/\/$/,"")}function me(){return(process.env.SHIELDLY_WEB_URL||ye).replace(/\/$/,"")}async function C(e,s){let o=me(),n=await fetch(`${o}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":O},body:JSON.stringify(s)});if(n.status===429)throw new Error("Demo rate limit reached. Get a free API key for higher limits: https://www.shieldly.io/app/api");if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function A(e,s,o){let n=N(),t=await fetch(`${n}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":O,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(t.status===202){let i=await t.json().catch(()=>({}));if(i.jobId)return ue(i.jobId,o);throw new Error("Analysis queued but no job ID returned \u2014 try again")}if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}async function ue(e,s){let o=[2e3,3e3,5e3],n=Date.now(),t=0;for(let i=0;i<180;i++){let l=o[Math.min(i,o.length-1)];await new Promise(r=>setTimeout(r,l));let y=Math.round((Date.now()-n)/1e3);process.stderr.write(`\rAI-Powered analysis in progress\u2026 (${y}s)`);let a;try{a=await T(`/v1/jobs/${encodeURIComponent(e)}`,s),t=0}catch(r){if(++t>=3)throw process.stderr.write(`
8
- `),r;continue}if(a.status==="complete")return process.stderr.write(`
9
- `),{...a.result,unitInfo:a.unitInfo};if(a.status==="failed")throw process.stderr.write(`
10
- `),new Error(a.error||"Analysis failed")}throw process.stderr.write(`
11
- `),new Error("Analysis timed out after polling")}async function T(e,s){let o=N(),n=await fetch(`${o}${e}`,{headers:{Authorization:`Bearer ${s}`,"User-Agent":O}});if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function V(e,s,o){let n=N(),t=await fetch(`${n}${e}`,{method:"DELETE",headers:{"Content-Type":"application/json","User-Agent":O,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}function R(e){return(0,$.existsSync)(e)||(console.error(`Error: File not found: ${e}`),process.exit(1)),(0,$.statSync)(e).isDirectory()&&(console.error(`Error: ${e} is a directory, not a file.
12
- For a CDK output directory, use: shieldly analyze-cf ${e}`),process.exit(1)),(0,$.readFileSync)(e,"utf8")}var F={CRITICAL:"\x1B[31m",HIGH:"\x1B[33m",MEDIUM:"\x1B[36m",LOW:"\x1B[32m",INFO:"\x1B[90m"},d="\x1B[0m",E="\x1B[1m",w="\x1B[2m",J="\x1B[36m";function P(e,s){if(s==="json"){console.log(JSON.stringify(e,null,2));return}let{score:o,riskLevel:n,findings:t=[],cached:i,summary:l,positives:y=[],unitInfo:a}=e,r=o==null?"\u2014":`${o}/100`;if(console.log(""),console.log(`${E}AI-Powered Security Analysis \u2014 Shieldly${d}`),console.log(`${w}${"\u2500".repeat(50)}${d}`),console.log(` ${E}Security Score:${d} ${fe(o)}${r}${d}`),console.log(` ${E}Risk Level:${d} ${he(n)}${n||"Unknown"}${d}`),i&&console.log(` ${w}(cached result)${d}`),l&&(console.log(""),console.log(` ${w}${l}${d}`)),console.log(""),y.length>0){console.log(`${E}What's good:${d}`);for(let c of y)console.log(` ${F.LOW}[+]${d} ${w}${c}${d}`);console.log("")}if(t.length===0)console.log(` ${J}[PASS] No findings${d}`);else{console.log(`${E}Findings (${t.length}):${d}`);for(let c of t){let u=F[(c.severity||"").toUpperCase()]||"";console.log(`
13
- ${u}[${c.severity}]${d} ${E}${c.title}${d}`),c.resource&&c.resource!=="*"&&console.log(` ${w}Resource: ${c.resource}${d}`),c.description&&console.log(` ${w}${c.description}${d}`),c.remediation&&console.log(` ${J}Fix:${d} ${c.remediation}`)}}a&&typeof a.unitsUsed=="number"&&typeof a.cap=="number"&&(console.log(""),console.log(` ${w}Units used: ${a.unitsUsed}/${a.cap}${d}`)),e.demoInfo&&typeof e.demoInfo.analysesRemaining=="number"&&(console.log(""),console.log(` ${w}Demo analyses remaining: ${e.demoInfo.analysesRemaining}. Get a free key for more: https://www.shieldly.io/app/api${d}`)),console.log("")}function fe(e){return e==null?"":e>=80?"\x1B[32m":e>=50?"\x1B[33m":"\x1B[31m"}function he(e){let s=(e||"").toUpperCase();return F[s]||""}var $e=`
2
+ var ie=Object.create;var W=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var ce=Object.getPrototypeOf,pe=Object.prototype.hasOwnProperty;var de=(e,s,o,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of ae(s))!pe.call(e,t)&&t!==o&&W(e,t,{get:()=>s[t],enumerable:!(n=le(s,t))||n.enumerable});return e};var B=(e,s,o)=>(o=e!=null?ie(ce(e)):{},de(s||!e||!e.__esModule?W(o,"default",{value:e,enumerable:!0}):o,e));var k=require("node:fs"),Y=require("node:path");var I=require("node:fs"),q=require("node:os"),j=require("node:path"),D="Shieldly-CLI/1.0.5",F=(0,j.join)((0,q.homedir)(),".shieldly","config.json"),ye="https://api.shieldly.io",me="https://www.shieldly.io",K=5;function N(){try{return JSON.parse((0,I.readFileSync)(F,"utf8"))}catch{return{}}}function ue(e){try{(0,I.mkdirSync)((0,j.dirname)(F),{recursive:!0}),(0,I.writeFileSync)(F,`${JSON.stringify(e,null,2)}
3
+ `)}catch{}}function P(e){return e||(process.env.SHIELDLY_API_KEY?process.env.SHIELDLY_API_KEY:N().apiKey||null)}function V(){let e=N().demoCount;return typeof e=="number"&&e>0?e:0}function _(){return V()>=K}function M(){let e=N();return e.demoCount=V()+1,ue(e),Math.max(0,K-e.demoCount)}function R(){return`You've used all ${K} free demo analyses.
4
+
5
+ Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api
6
+
7
+ Then set SHIELDLY_API_KEY or pass --api-key.`}function T(){return(process.env.SHIELDLY_API_URL||ye).replace(/\/$/,"")}function Z(){return(process.env.SHIELDLY_WEB_URL||me).replace(/\/$/,"")}function C(e){let s=Z(),o=e==null?"":`?score=${encodeURIComponent(e)}`;return`[![Shieldly Security Score](${`${s}/api/badge${o}`})](${s})`}async function v(e,s){let o=Z(),n=await fetch(`${o}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":D},body:JSON.stringify(s)});if(n.status===429)throw new Error("Demo rate limit reached. Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api");if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function x(e,s,o){let n=T(),t=await fetch(`${n}${e}`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":D,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(t.status===202){let i=await t.json().catch(()=>({}));if(i.jobId)return fe(i.jobId,o);throw new Error("Analysis queued but no job ID returned \u2014 try again")}if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}async function fe(e,s){let o=[2e3,3e3,5e3],n=Date.now(),t=0;for(let i=0;i<180;i++){let l=o[Math.min(i,o.length-1)];await new Promise(r=>setTimeout(r,l));let d=Math.round((Date.now()-n)/1e3);process.stderr.write(`\rAI-Powered analysis in progress\u2026 (${d}s)`);let c;try{c=await G(`/v1/jobs/${encodeURIComponent(e)}`,s),t=0}catch(r){if(++t>=3)throw process.stderr.write(`
8
+ `),r;continue}if(c.status==="complete")return process.stderr.write(`
9
+ `),{...c.result,unitInfo:c.unitInfo};if(c.status==="failed")throw process.stderr.write(`
10
+ `),new Error(c.error||"Analysis failed")}throw process.stderr.write(`
11
+ `),new Error("Analysis timed out after polling")}async function G(e,s){let o=T(),n=await fetch(`${o}${e}`,{headers:{Authorization:`Bearer ${s}`,"User-Agent":D}});if(!n.ok){let t=await n.json().catch(()=>({}));throw new Error(t.error||`API error ${n.status}`)}return n.json()}async function Q(e,s,o){let n=T(),t=await fetch(`${n}${e}`,{method:"DELETE",headers:{"Content-Type":"application/json","User-Agent":D,Authorization:`Bearer ${o}`},body:JSON.stringify(s)});if(!t.ok){let i=await t.json().catch(()=>({}));throw new Error(i.error||`API error ${t.status}`)}return t.json()}function U(e){return(0,I.existsSync)(e)||(console.error(`Error: File not found: ${e}`),process.exit(1)),(0,I.statSync)(e).isDirectory()&&(console.error(`Error: ${e} is a directory, not a file.
12
+ For a CDK output directory, use: shieldly analyze-cf ${e}`),process.exit(1)),(0,I.readFileSync)(e,"utf8")}var H={CRITICAL:"\x1B[31m",HIGH:"\x1B[33m",MEDIUM:"\x1B[36m",LOW:"\x1B[32m",INFO:"\x1B[90m"},y="\x1B[0m",S="\x1B[1m",b="\x1B[2m",J="\x1B[36m";function L(e,s){if(s==="json"){console.log(JSON.stringify(e,null,2));return}let{score:o,riskLevel:n,findings:t=[],cached:i,summary:l,positives:d=[],unitInfo:c}=e,r=o==null?"\u2014":`${o}/100`;if(console.log(""),console.log(`${S}AI-Powered Security Analysis \u2014 Shieldly${y}`),console.log(`${b}${"\u2500".repeat(50)}${y}`),console.log(` ${S}Security Score:${y} ${he(o)}${r}${y}`),console.log(` ${S}Risk Level:${y} ${$e(n)}${n||"Unknown"}${y}`),i&&console.log(` ${b}(cached result)${y}`),l&&(console.log(""),console.log(` ${b}${l}${y}`)),console.log(""),d.length>0){console.log(`${S}What's good:${y}`);for(let p of d)console.log(` ${H.LOW}[+]${y} ${b}${p}${y}`);console.log("")}if(t.length===0)console.log(` ${J}[PASS] No findings${y}`);else{console.log(`${S}Findings (${t.length}):${y}`);for(let p of t){let h=H[(p.severity||"").toUpperCase()]||"";console.log(`
13
+ ${h}[${p.severity}]${y} ${S}${p.title}${y}`),p.resource&&p.resource!=="*"&&console.log(` ${b}Resource: ${p.resource}${y}`),p.description&&console.log(` ${b}${p.description}${y}`),p.remediation&&console.log(` ${J}Fix:${y} ${p.remediation}`)}}c&&typeof c.unitsUsed=="number"&&typeof c.cap=="number"&&(console.log(""),console.log(` ${b}Units used: ${c.unitsUsed}/${c.cap}${y}`)),e.demoInfo&&typeof e.demoInfo.analysesRemaining=="number"&&(console.log(""),console.log(` ${b}Demo analyses remaining: ${e.demoInfo.analysesRemaining}. Get an API key (Builder plan or above) for more: https://www.shieldly.io/app/api${y}`)),console.log("")}function he(e){return e==null?"":e>=80?"\x1B[32m":e>=50?"\x1B[33m":"\x1B[31m"}function $e(e){let s=(e||"").toUpperCase();return H[s]||""}var ge=`
14
14
  Usage: shieldly analyze-cf <template-file-or-dir> [options]
15
15
 
16
16
  Analyze a CloudFormation template (or directory of templates) for security issues using AI.
@@ -23,11 +23,12 @@ Arguments:
23
23
  Options:
24
24
  --format <fmt> Output format: table | json (default: table)
25
25
  --api-key <key> API key (or set SHIELDLY_API_KEY env var)
26
+ --badge Print a shareable score badge (Markdown, for a README)
26
27
  -h, --help Show this help
27
28
 
28
29
  Authentication:
29
30
  No key needed for demo mode (rate-limited). Set SHIELDLY_API_KEY for full access.
30
- Get a free key: https://www.shieldly.io/app/api
31
+ Get an API key (Builder plan or above): https://www.shieldly.io/app/api
31
32
 
32
33
  Examples:
33
34
  shieldly analyze-cf template.json
@@ -43,13 +44,13 @@ cdk.json hook (runs after every cdk synth):
43
44
  "afterSynth": ["sh", "-c", "shieldly analyze-cf cdk.out/ || true"]
44
45
  }
45
46
  }
46
- `;function ge(e){try{let s=(0,k.readFileSync)((0,Y.join)(e,"manifest.json"),"utf8"),o=JSON.parse(s);if(!o.artifacts||typeof o.artifacts!="object")return null;let n=Object.values(o.artifacts).filter(t=>t.type==="aws:cloudformation:stack"&&t.properties?.templateFile).map(t=>(0,Y.join)(e,t.properties.templateFile)).filter(t=>(0,k.existsSync)(t));return n.length>0?n:null}catch{return null}}function Ie(e){let s=ge(e);if(s)return s;let o=[],n;try{n=(0,k.readdirSync)(e,{withFileTypes:!0})}catch{return o}for(let t of n){if(!t.isFile())continue;let i=t.name;(i.endsWith(".template.json")||i.endsWith(".template.yaml"))&&o.push((0,Y.join)(e,i))}return o}async function ke(e,s,o,n,t){let i=(0,k.readFileSync)(e,"utf8");o!=="json"&&t>1?process.stdout.write(`[${n}/${t}] Analyzing ${e}\u2026
47
+ `;function Ie(e){try{let s=(0,k.readFileSync)((0,Y.join)(e,"manifest.json"),"utf8"),o=JSON.parse(s);if(!o.artifacts||typeof o.artifacts!="object")return null;let n=Object.values(o.artifacts).filter(t=>t.type==="aws:cloudformation:stack"&&t.properties?.templateFile).map(t=>(0,Y.join)(e,t.properties.templateFile)).filter(t=>(0,k.existsSync)(t));return n.length>0?n:null}catch{return null}}function ke(e){let s=Ie(e);if(s)return s;let o=[],n;try{n=(0,k.readdirSync)(e,{withFileTypes:!0})}catch{return o}for(let t of n){if(!t.isFile())continue;let i=t.name;(i.endsWith(".template.json")||i.endsWith(".template.yaml"))&&o.push((0,Y.join)(e,i))}return o}async function be(e,s,o,n,t){let i=(0,k.readFileSync)(e,"utf8");o!=="json"&&t>1?process.stdout.write(`[${n}/${t}] Analyzing ${e}\u2026
47
48
  `):o!=="json"&&process.stdout.write(`Analyzing ${e}\u2026
48
- `);let l=s?await A("/v1/analyze/cf",{template:i},s):await C("/api/demo/analyze-iam",{template:i,policyType:"cf"});return!s&&!l.cached&&j(),{filePath:e,data:l}}async function Z(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log($e);return}let s=e.indexOf("--format"),o=s!==-1?e[s+1]:"table";s!==-1&&!["table","json"].includes(o)&&(console.error(`Error: invalid --format "${o}". Use: table | json`),process.exit(1));let n=e.indexOf("--api-key"),t=S(n!==-1?e[n+1]:null),i=new Set;for(let r of[s,n])r!==-1&&(i.add(r),i.add(r+1));let l=e.find((r,c)=>!i.has(c)&&!r.startsWith("--"));l||(console.error("Error: template-file-or-dir argument is required"),process.exit(1)),!t&&_()&&(console.error(D()),process.exit(1)),!t&&o!=="json"&&console.log("Demo mode (rate-limited, no signup required). Get a free API key for higher limits: https://www.shieldly.io/app/api");let y=(0,k.statSync)(l,{throwIfNoEntry:!1});if(y||(console.error(`Error: path not found: ${l}`),process.exit(1)),y.isDirectory()){let r=Ie(l);r.length===0&&(console.error(`Error: no CloudFormation templates (*.template.json / *.template.yaml) found in ${l}
49
+ `);let l=s?await x("/v1/analyze/cf",{template:i},s):await v("/api/demo/analyze-iam",{template:i,policyType:"cf"});return!s&&!l.cached&&M(),{filePath:e,data:l}}async function X(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(ge);return}let s=e.indexOf("--format"),o=s!==-1?e[s+1]:"table";s!==-1&&!["table","json"].includes(o)&&(console.error(`Error: invalid --format "${o}". Use: table | json`),process.exit(1));let n=e.indexOf("--api-key"),t=P(n!==-1?e[n+1]:null),i=new Set;for(let r of[s,n])r!==-1&&(i.add(r),i.add(r+1));let l=e.find((r,p)=>!i.has(p)&&!r.startsWith("--"));l||(console.error("Error: template-file-or-dir argument is required"),process.exit(1)),!t&&_()&&(console.error(R()),process.exit(1)),!t&&o!=="json"&&console.log("Demo mode (rate-limited, no signup required). Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api");let d=(0,k.statSync)(l,{throwIfNoEntry:!1});if(d||(console.error(`Error: path not found: ${l}`),process.exit(1)),d.isDirectory()){let r=ke(l);r.length===0&&(console.error(`Error: no CloudFormation templates (*.template.json / *.template.yaml) found in ${l}
49
50
  Run "cdk synth" first to generate stack templates.`),process.exit(1)),o!=="json"&&console.log(`Found ${r.length} stack template(s) in ${l}
50
- `);let c=[],u=0,p=0;for(let f=0;f<r.length;f++){if(!t&&_()){o!=="json"&&console.error(`
51
- Demo allowance reached \u2014 remaining stacks skipped. Get a free key: https://www.shieldly.io/app/api`);break}try{let{filePath:g,data:I}=await ke(r[f],t,o,f+1,r.length);c.push({filePath:g,data:I}),u+=(I.findings||[]).filter(M=>M.severity?.toUpperCase()==="CRITICAL").length,p+=(I.findings||[]).filter(M=>M.severity?.toUpperCase()==="HIGH").length,o!=="json"&&P(I,o)}catch(g){if(console.error(`Error analyzing ${r[f]}: ${g.message}`),!t&&/rate limit/i.test(g.message))break}}if(o==="json")console.log(JSON.stringify(c.map(({filePath:f,data:g})=>({stack:f,...g})),null,2));else if(r.length>1){let f=c.reduce((g,I)=>g+(I.data.findings||[]).length,0);console.log(`
52
- Summary: ${r.length} stacks \xB7 ${f} total findings \xB7 ${u} critical \xB7 ${p} high`)}(u>0||p>0)&&process.exit(1);return}let a=R(l);o!=="json"&&console.log(`Analyzing ${l}\u2026`);try{let r=t?await A("/v1/analyze/cf",{template:a},t):await C("/api/demo/analyze-iam",{template:a,policyType:"cf"});P(r,o);let c=(r.findings||[]).filter(p=>p.severity?.toUpperCase()==="CRITICAL").length,u=(r.findings||[]).filter(p=>p.severity?.toUpperCase()==="HIGH").length;(c>0||u>0)&&process.exit(1)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}}var we=`
51
+ `);let p=[],h=0,a=0;for(let u=0;u<r.length;u++){if(!t&&_()){o!=="json"&&console.error(`
52
+ Demo allowance reached \u2014 remaining stacks skipped. Get an API key (Builder plan or above): https://www.shieldly.io/app/api`);break}try{let{filePath:$,data:f}=await be(r[u],t,o,u+1,r.length);p.push({filePath:$,data:f}),h+=(f.findings||[]).filter(A=>A.severity?.toUpperCase()==="CRITICAL").length,a+=(f.findings||[]).filter(A=>A.severity?.toUpperCase()==="HIGH").length,o!=="json"&&L(f,o)}catch($){if(console.error(`Error analyzing ${r[u]}: ${$.message}`),!t&&/rate limit/i.test($.message))break}}if(o==="json")console.log(JSON.stringify(p.map(({filePath:u,data:$})=>({stack:u,...$})),null,2));else if(r.length>1){let u=p.reduce(($,f)=>$+(f.data.findings||[]).length,0);console.log(`
53
+ Summary: ${r.length} stacks \xB7 ${u} total findings \xB7 ${h} critical \xB7 ${a} high`)}if(e.includes("--badge")&&p.length>0){let u=p.map(({data:A})=>A.score).filter(A=>typeof A=="number"),$=u.length>0?Math.min(...u):null,f=C($);o==="json"?console.error(f):console.log(f)}(h>0||a>0)&&process.exit(1);return}let c=U(l);o!=="json"&&console.log(`Analyzing ${l}\u2026`);try{let r=t?await x("/v1/analyze/cf",{template:c},t):await v("/api/demo/analyze-iam",{template:c,policyType:"cf"});if(L(r,o),e.includes("--badge")){let a=C(r.score);o==="json"?console.error(a):console.log(a)}let p=(r.findings||[]).filter(a=>a.severity?.toUpperCase()==="CRITICAL").length,h=(r.findings||[]).filter(a=>a.severity?.toUpperCase()==="HIGH").length;(p>0||h>0)&&process.exit(1)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}}var we=`
53
54
  Usage: shieldly analyze-iam <policy-file> [options]
54
55
 
55
56
  Analyze an AWS IAM policy for security issues using AI.
@@ -63,13 +64,15 @@ Options:
63
64
  'cross_account' expects {"identityPolicy":\u2026,"trustPolicy":\u2026}.
64
65
  --format <fmt> Output format: table | json (default: table)
65
66
  --api-key <key> API key (or set SHIELDLY_API_KEY env var)
67
+ --badge Print a shareable score badge (Markdown, for a README)
66
68
  -h, --help Show this help
67
69
 
68
70
  Examples:
69
71
  shieldly analyze-iam policy.json
70
72
  shieldly analyze-iam policy.json --type cross_account --format json
73
+ shieldly analyze-iam policy.json --badge
71
74
  SHIELDLY_API_KEY=sk_... shieldly analyze-iam policy.json
72
- `;async function Q(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(we);return}let s=e.indexOf("--type"),o=s!==-1?e[s+1]:"identity",n=e.indexOf("--format"),t=n!==-1?e[n+1]:"table";s!==-1&&!["identity","iam_identity","cross_account"].includes(o)&&(console.error(`Error: invalid --type "${o}". Use: identity | cross_account`),process.exit(1)),n!==-1&&!["table","json"].includes(t)&&(console.error(`Error: invalid --format "${t}". Use: table | json`),process.exit(1));let i=e.indexOf("--api-key"),l=S(i!==-1?e[i+1]:null);!l&&_()&&(console.error(D()),process.exit(1));let y=new Set;for(let p of[s,n,i])p!==-1&&(y.add(p),y.add(p+1));let a=e.find((p,f)=>!y.has(f)&&!p.startsWith("--"));a||(console.error("Error: policy-file argument is required"),process.exit(1));let r=R(a);try{JSON.parse(r)}catch{console.error("Error: policy-file must be valid JSON"),process.exit(1)}let c=r.trim(),u=o==="identity"||o==="iam_identity"?"iam_identity":o==="cross_account"?"cross_account":"iam_identity";t!=="json"&&(console.log(`Analyzing ${a} (type: ${u})\u2026`),l||console.log("Demo mode (rate-limited, no signup required). Get a free API key for higher limits: https://www.shieldly.io/app/api"));try{let p=l?await A("/v1/analyze/iam",{policy:c,policyType:u},l):await C("/api/demo/analyze-iam",{policy:c,policyType:u});!l&&!p.cached&&j(),P(p,t);let f=(p.findings||[]).filter(I=>I.severity?.toUpperCase()==="CRITICAL").length,g=(p.findings||[]).filter(I=>I.severity?.toUpperCase()==="HIGH").length;(f>0||g>0)&&process.exit(1)}catch(p){console.error(`Error: ${p.message}`),process.exit(1)}}var X=`
75
+ `;async function ee(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(we);return}let s=e.indexOf("--type"),o=s!==-1?e[s+1]:"identity",n=e.indexOf("--format"),t=n!==-1?e[n+1]:"table";s!==-1&&!["identity","iam_identity","cross_account"].includes(o)&&(console.error(`Error: invalid --type "${o}". Use: identity | cross_account`),process.exit(1)),n!==-1&&!["table","json"].includes(t)&&(console.error(`Error: invalid --format "${t}". Use: table | json`),process.exit(1));let i=e.indexOf("--api-key"),l=P(i!==-1?e[i+1]:null);!l&&_()&&(console.error(R()),process.exit(1));let d=new Set;for(let a of[s,n,i])a!==-1&&(d.add(a),d.add(a+1));let c=e.find((a,u)=>!d.has(u)&&!a.startsWith("--"));c||(console.error("Error: policy-file argument is required"),process.exit(1));let r=U(c);try{JSON.parse(r)}catch{console.error("Error: policy-file must be valid JSON"),process.exit(1)}let p=r.trim(),h=o==="identity"||o==="iam_identity"?"iam_identity":o==="cross_account"?"cross_account":"iam_identity";t!=="json"&&(console.log(`Analyzing ${c} (type: ${h})\u2026`),l||console.log("Demo mode (rate-limited, no signup required). Get an API key (Builder plan or above) for higher limits: https://www.shieldly.io/app/api"));try{let a=l?await x("/v1/analyze/iam",{policy:p,policyType:h},l):await v("/api/demo/analyze-iam",{policy:p,policyType:h});if(!l&&!a.cached&&M(),L(a,t),e.includes("--badge")){let f=C(a.score);t==="json"?console.error(f):console.log(f)}let u=(a.findings||[]).filter(f=>f.severity?.toUpperCase()==="CRITICAL").length,$=(a.findings||[]).filter(f=>f.severity?.toUpperCase()==="HIGH").length;(u>0||$>0)&&process.exit(1)}catch(a){console.error(`Error: ${a.message}`),process.exit(1)}}var oe=`
73
76
  Usage: shieldly api-keys <subcommand> [options]
74
77
 
75
78
  Manage Shieldly API keys.
@@ -89,12 +92,12 @@ Examples:
89
92
  shieldly api-keys list
90
93
  shieldly api-keys create --label "CI/CD Key" --scopes iam,cf
91
94
  shieldly api-keys revoke key_abc123
92
- `,x="\x1B[1m",ee="\x1B[2m",oe="\x1B[36m",h="\x1B[0m";function xe(e){return e?new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"\u2014"}async function te(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(X);return}let s=e[0],o=e.slice(1),n=o.indexOf("--api-key"),t=S(n!==-1?o[n+1]:null),i=o.indexOf("--format"),l=i!==-1?o[i+1]:"table";if(i!==-1&&!["table","json"].includes(l)&&(console.error(`Error: invalid --format "${l}". Use: table | json`),process.exit(1)),t||(console.error(`API key management requires an API key to authenticate.
95
+ `,w="\x1B[1m",te="\x1B[2m",se="\x1B[36m",g="\x1B[0m";function Ae(e){return e?new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"\u2014"}async function ne(e){if(e.includes("-h")||e.includes("--help")||e.length===0){console.log(oe);return}let s=e[0],o=e.slice(1),n=o.indexOf("--api-key"),t=P(n!==-1?o[n+1]:null),i=o.indexOf("--format"),l=i!==-1?o[i+1]:"table";if(i!==-1&&!["table","json"].includes(l)&&(console.error(`Error: invalid --format "${l}". Use: table | json`),process.exit(1)),t||(console.error(`API key management requires an API key to authenticate.
93
96
 
94
- Create your free account: https://www.shieldly.io/app/api
97
+ Get an API key (Builder plan or above): https://www.shieldly.io/app/api
95
98
 
96
- Set SHIELDLY_API_KEY or use --api-key once you have your key.`),process.exit(1)),s==="list"){try{let a=(await T("/v1/api-keys",t)).keys||[];if(l==="json"){console.log(JSON.stringify(a,null,2));return}if(a.length===0){console.log("No API keys found. Create one at https://www.shieldly.io/app/api");return}console.log(""),console.log(`${x}API Keys (${a.length}):${h}`),console.log(`${ee}${"\u2500".repeat(60)}${h}`);for(let r of a){let c=(r.scopes||[]).join(", ")||"all";console.log(` ${oe}${r.keyId}${h}`),console.log(` ${x}Label:${h} ${r.label||"(unlabeled)"}`),console.log(` ${x}Scopes:${h} ${c}`),console.log(` ${x}Uses:${h} ${r.usageCount||0}`),console.log(` ${x}Created:${h} ${xe(r.createdAt)}`),console.log("")}}catch(y){console.error(`Error: ${y.message}`),process.exit(1)}return}if(s==="create"){let y=o.indexOf("--label"),a=y!==-1?o[y+1]:"CLI Key",r=o.indexOf("--scopes"),u=(r!==-1?o[r+1]:"iam,cf").split(",").map(p=>p.trim()).filter(Boolean);try{let p=await A("/v1/api-keys",{label:a,scopes:u},t);if(l==="json"){console.log(JSON.stringify(p,null,2));return}console.log(""),console.log(`${x}[OK] API key created${h}`),console.log(` ${x}Key ID:${h} ${p.keyId}`),console.log(` ${x}API Key:${h} ${oe}${p.apiKey}${h}`),console.log(` ${ee}Store this key securely \u2014 it won't be shown again.${h}`),console.log("")}catch(p){console.error(`Error: ${p.message}`),process.exit(1)}return}if(s==="revoke"){let y=new Set;n!==-1&&(y.add(n),y.add(n+1)),i!==-1&&(y.add(i),y.add(i+1));let a=o.find((r,c)=>!y.has(c)&&!r.startsWith("--"));a||(console.error(`Error: key-id argument is required
97
- Usage: shieldly api-keys revoke <key-id>`),process.exit(1));try{if(await V("/v1/api-keys",{keyId:a},t),l==="json"){console.log(JSON.stringify({success:!0,keyId:a}));return}console.log(`[OK] API key ${a} revoked`)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}return}console.error(`Unknown subcommand: ${s}`),console.log(X),process.exit(1)}var Ae=`
99
+ Set SHIELDLY_API_KEY or use --api-key once you have your key.`),process.exit(1)),s==="list"){try{let c=(await G("/v1/api-keys",t)).keys||[];if(l==="json"){console.log(JSON.stringify(c,null,2));return}if(c.length===0){console.log("No API keys found. Create one at https://www.shieldly.io/app/api");return}console.log(""),console.log(`${w}API Keys (${c.length}):${g}`),console.log(`${te}${"\u2500".repeat(60)}${g}`);for(let r of c){let p=(r.scopes||[]).join(", ")||"all";console.log(` ${se}${r.keyId}${g}`),console.log(` ${w}Label:${g} ${r.label||"(unlabeled)"}`),console.log(` ${w}Scopes:${g} ${p}`),console.log(` ${w}Uses:${g} ${r.usageCount||0}`),console.log(` ${w}Created:${g} ${Ae(r.createdAt)}`),console.log("")}}catch(d){console.error(`Error: ${d.message}`),process.exit(1)}return}if(s==="create"){let d=o.indexOf("--label"),c=d!==-1?o[d+1]:"CLI Key",r=o.indexOf("--scopes"),h=(r!==-1?o[r+1]:"iam,cf").split(",").map(a=>a.trim()).filter(Boolean);try{let a=await x("/v1/api-keys",{label:c,scopes:h},t);if(l==="json"){console.log(JSON.stringify(a,null,2));return}console.log(""),console.log(`${w}[OK] API key created${g}`),console.log(` ${w}Key ID:${g} ${a.keyId}`),console.log(` ${w}API Key:${g} ${se}${a.apiKey}${g}`),console.log(` ${te}Store this key securely \u2014 it won't be shown again.${g}`),console.log("")}catch(a){console.error(`Error: ${a.message}`),process.exit(1)}return}if(s==="revoke"){let d=new Set;n!==-1&&(d.add(n),d.add(n+1)),i!==-1&&(d.add(i),d.add(i+1));let c=o.find((r,p)=>!d.has(p)&&!r.startsWith("--"));c||(console.error(`Error: key-id argument is required
100
+ Usage: shieldly api-keys revoke <key-id>`),process.exit(1));try{if(await Q("/v1/api-keys",{keyId:c},t),l==="json"){console.log(JSON.stringify({success:!0,keyId:c}));return}console.log(`[OK] API key ${c} revoked`)}catch(r){console.error(`Error: ${r.message}`),process.exit(1)}return}console.error(`Unknown subcommand: ${s}`),console.log(oe),process.exit(1)}var xe=`
98
101
  _shieldly() {
99
102
  local cur prev words cword
100
103
  _init_completion || return
@@ -154,7 +157,7 @@ _shieldly() {
154
157
  fi
155
158
  } &&
156
159
  complete -F _shieldly shieldly
157
- `,be=`
160
+ `,Ee=`
158
161
  #compdef shieldly
159
162
 
160
163
  _shieldly() {
@@ -225,7 +228,7 @@ _shieldly() {
225
228
  }
226
229
 
227
230
  _shieldly
228
- `,Ee=`
231
+ `,Se=`
229
232
  Usage: shieldly completion <shell>
230
233
 
231
234
  Generate shell completion scripts for the shieldly CLI.
@@ -239,45 +242,45 @@ Examples:
239
242
  eval "$(shieldly completion bash)" # Source bash completion
240
243
  shieldly completion zsh > /usr/local/share/zsh/site-functions/_shieldly # Install zsh completion
241
244
  shieldly completion install # Auto-detect and install
242
- `;async function se(e){if(!e.length||e.includes("-h")||e.includes("--help")){console.log(Ee);return}let s=e[0];switch(s){case"bash":console.log(Ae.trimStart());break;case"zsh":console.log(be.trimStart());break;case"install":await Se();break;default:console.error(`Unsupported shell: ${s}`),console.log("Supported shells: bash, zsh"),process.exit(1)}}async function Se(){let e=process.env.SHELL||"",s=process.env.HOME||"";if(e.includes("zsh")){let o=`${s}/.zshrc`,n=`
245
+ `;async function re(e){if(!e.length||e.includes("-h")||e.includes("--help")){console.log(Se);return}let s=e[0];switch(s){case"bash":console.log(xe.trimStart());break;case"zsh":console.log(Ee.trimStart());break;case"install":await Pe();break;default:console.error(`Unsupported shell: ${s}`),console.log("Supported shells: bash, zsh"),process.exit(1)}}async function Pe(){let e=process.env.SHELL||"",s=process.env.HOME||"";if(e.includes("zsh")){let o=`${s}/.zshrc`,n=`
243
246
  # shieldly CLI completion
244
247
  autoload -Uz compinit && compinit -C 2>/dev/null
245
248
  eval "$(shieldly completion zsh)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.zshrc");return}i(o,n),console.log("Installed shieldly completion in ~/.zshrc"),console.log("Run: source ~/.zshrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else if(e.includes("bash")){let o=`${s}/.bashrc`,n=`
246
249
  # shieldly CLI completion
247
- source /dev/stdin <<< "$(shieldly completion bash)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.bashrc");return}i(o,n),console.log("Installed shieldly completion in ~/.bashrc"),console.log("Run: source ~/.bashrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else console.error(`Unsupported shell: ${e}. Install manually:`),console.log(' eval "$(shieldly completion bash)" # For bash'),console.log(' eval "$(shieldly completion zsh)" # For zsh'),process.exit(1)}var b="\x1B[1m",L="\x1B[36m",v="\x1B[2m",m="\x1B[0m",_e="1.0.3",Ce=`
248
- ${b}shieldly${m} \u2014 AI-Powered Security Analysis for AWS
250
+ source /dev/stdin <<< "$(shieldly completion bash)" 2>/dev/null`;try{let{readFileSync:t,appendFileSync:i,existsSync:l}=await import("node:fs");if(l(o)&&t(o,"utf8").includes("shieldly completion")){console.log("shieldly completion already installed in ~/.bashrc");return}i(o,n),console.log("Installed shieldly completion in ~/.bashrc"),console.log("Run: source ~/.bashrc")}catch(t){console.error(`Failed to install: ${t.message}`),process.exit(1)}}else console.error(`Unsupported shell: ${e}. Install manually:`),console.log(' eval "$(shieldly completion bash)" # For bash'),console.log(' eval "$(shieldly completion zsh)" # For zsh'),process.exit(1)}var E="\x1B[1m",z="\x1B[36m",O="\x1B[2m",m="\x1B[0m",_e="1.0.5",Ce=`
251
+ ${E}shieldly${m} \u2014 AI-Powered Security Analysis for AWS
249
252
 
250
- ${b}Usage:${m}
253
+ ${E}Usage:${m}
251
254
  shieldly <command> [args] [options]
252
255
 
253
- ${b}Commands:${m}
254
- ${L}analyze-iam${m} <policy-file> Analyze an IAM policy for security issues
255
- ${L}analyze-cf${m} <template-file-or-dir> Analyze a CloudFormation template or CDK output directory
256
- ${L}api-keys${m} list|create|revoke Manage API keys
257
- ${L}completion${m} bash|zsh|install Generate shell completion
256
+ ${E}Commands:${m}
257
+ ${z}analyze-iam${m} <policy-file> Analyze an IAM policy for security issues
258
+ ${z}analyze-cf${m} <template-file-or-dir> Analyze a CloudFormation template or CDK output directory
259
+ ${z}api-keys${m} list|create|revoke Manage API keys
260
+ ${z}completion${m} bash|zsh|install Generate shell completion
258
261
 
259
- ${b}Global Options:${m}
262
+ ${E}Global Options:${m}
260
263
  --api-key <key> API key (or set SHIELDLY_API_KEY env var)
261
264
  --version Show version
262
265
  -h, --help Show this help
263
266
 
264
- ${b}Authentication:${m}
267
+ ${E}Authentication:${m}
265
268
  Set your API key via env var: export SHIELDLY_API_KEY=sk_...
266
- Get a free API key at: ${L}https://www.shieldly.io/app/api${m}
269
+ Get an API key (Builder plan or above) at: ${z}https://www.shieldly.io/app/api${m}
267
270
 
268
- ${b}Examples:${m}
269
- ${v}# Analyze an IAM policy or CF template (no API key needed \u2014 demo mode)${m}
271
+ ${E}Examples:${m}
272
+ ${O}# Analyze an IAM policy or CF template (no API key needed \u2014 demo mode)${m}
270
273
  shieldly analyze-iam policy.json
271
274
 
272
- ${v}# Analyze a single CloudFormation template${m}
275
+ ${O}# Analyze a single CloudFormation template${m}
273
276
  shieldly analyze-cf template.json
274
277
 
275
- ${v}# Scan all CDK stacks after synthesis (reads manifest.json \u2014 current stacks only)${m}
278
+ ${O}# Scan all CDK stacks after synthesis (reads manifest.json \u2014 current stacks only)${m}
276
279
  cdk synth && shieldly analyze-cf cdk.out/
277
280
 
278
- ${v}# List API keys${m}
281
+ ${O}# List API keys${m}
279
282
  shieldly api-keys list
280
283
 
281
- ${v}# Use in CI${m}
284
+ ${O}# Use in CI${m}
282
285
  SHIELDLY_API_KEY=\${{ secrets.SHIELDLY_API_KEY }} shieldly analyze-iam policy.json
283
- `;async function Pe(){let[,,e,...s]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(Ce),process.exit(0)),(e==="--version"||e==="-v")&&(console.log(_e),process.exit(0)),e){case"analyze-iam":await Q(s);break;case"analyze-cf":await Z(s);break;case"api-keys":await te(s);break;case"completion":await se(s);break;default:console.error(`Unknown command: ${e}`),console.log(`Run ${b}shieldly --help${m} for usage`),process.exit(1)}}Pe().catch(e=>{console.error("Fatal error:",e.message),process.exit(1)});
286
+ `;async function ve(){let[,,e,...s]=process.argv;switch((!e||e==="-h"||e==="--help")&&(console.log(Ce),process.exit(0)),(e==="--version"||e==="-v")&&(console.log(_e),process.exit(0)),e){case"analyze-iam":await ee(s);break;case"analyze-cf":await X(s);break;case"api-keys":await ne(s);break;case"completion":await re(s);break;default:console.error(`Unknown command: ${e}`),console.log(`Run ${E}shieldly --help${m} for usage`),process.exit(1)}}ve().catch(e=>{console.error("Fatal error:",e.message),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shieldly/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "AI-Powered Security Analysis for AWS — official CLI",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.shieldly.io",