You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+15-8Lines changed: 15 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,6 +28,7 @@ The core UX premise: paste whatever messy thing you grabbed from a log file, a n
28
28
A single `<textarea>` input. The `detect()` function determines whether the input is a JWT (three-part dot-separated base64url, or starts with `Authorization: Bearer`) or SAML (everything else). A debounced `$effect` drives decoding on a 300ms timer.
29
29
30
30
The page handles:
31
+
31
32
- Multiple SAML messages in one paste (`decodeAllSaml` scans for all `SAMLRequest`/`SAMLResponse` params)
32
33
- Generic fallback decoding (`decodeAllGeneric`) for non-SAML base64/XML/JSON blobs
33
34
- Lazy-loaded cert decoding via dynamic `import('$lib/cert')` to avoid blocking first render
@@ -60,6 +61,7 @@ The page handles:
60
61
### JWT decode library (`src/lib/jwt.ts`)
61
62
62
63
`decodeJwt(input)` — strips `Bearer` prefix, splits on `.`, base64url-decodes header and payload, returns typed `JwtDecodeResult` with:
64
+
63
65
-`isAlgNone` / `isWeakAlg` flags
64
66
-`timestamps` object (`iat`, `exp`, `nbf` as `JwtTimestamp` with `date`, `label`, `expired`)
65
67
-`scopes` array (handles both `scope` string and `scp` array claim variants)
@@ -105,6 +107,7 @@ All tooltip text externalized as `FIELD_EXPLANATIONS: Record<string, string>` wi
105
107
## Test coverage
106
108
107
109
100% statements, branches, functions, and lines across all `src/lib/` modules. Achieved via:
110
+
108
111
-`/* v8 ignore next */` directives on defensive null-guards that are unreachable in valid DER/XML (never in `parseName`, `parseKeyAlg`, etc.)
109
112
- Carefully crafted DER certificate fixtures (single-line base64, length divisible by 4 — jsdom's `atob()` is strict, unlike Node's `Buffer.from()`)
110
113
- Test cases for every edge path: UTCTime year ≥ 50, EC with no curve OID, RSA modulus without leading zero, unknown DN attribute OIDs, all SAML timestamp variants, JWT non-string alg, etc.
@@ -135,10 +138,12 @@ Run with: `npm run coverage`
135
138
## Planned features
136
139
137
140
### Medium priority
141
+
138
142
-**SAML signature validation** — fetch the IdP's SAML metadata (by EntityID or URL), extract the signing cert, and verify `<ds:Signature>` using the Web Crypto API. Display verified/unverified status prominently.
139
143
-**JWT JWKS validation** — after OIDC discovery, fetch `jwks_uri` and attempt to verify the JWT signature against the matching key.
140
144
141
145
### Lower priority / ideas
146
+
142
147
-**MDQ discovery** — fetch an entity's metadata from InCommon's MDQ service by EntityID and feed it into the metadata view (`src/lib/metadata.ts`)
143
148
-**REFEDS entity category checker** — cross-check an SP's `RequestedAttribute`s against entity categories already parsed by the metadata view
144
149
-**i18n** — `explanations.ts` is already structured for this; add a locale switcher and alternate record implementations
@@ -157,15 +162,15 @@ Optional body explaining why, not what.
All three must pass before reporting work complete.
191
198
192
199
ESLint config note: `svelte/no-navigation-without-resolve` is configured with `ignoreLinks: true` because samlguy.com deploys at the root with no base path, making `resolve()` unnecessary for plain `href="/"` links.
Copy file name to clipboardExpand all lines: README.md
+4-11Lines changed: 4 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,8 @@
1
1
<divalign="center">
2
2
3
3
# `<saml:Guy/>`
4
-
**[*samlguy.com*](https://samlguy.com) — A SAML assertion and JWT decoder for the IAM community.**
4
+
5
+
**[_samlguy.com_](https://samlguy.com) — A SAML assertion and JWT decoder for the IAM community.**
5
6
6
7
[](https://github.com/kellenmurphy/samlguy/actions/workflows/ci.yml)[](https://codecov.io/gh/kellenmurphy/samlguy)[](https://www.bestpractices.dev/projects/12859)
7
8
@@ -13,10 +14,9 @@ SAML assertions get a full summary panel with binding type, message type, status
13
14
14
15
Think [jwt.io](https://jwt.io), but for the full modern IAM stack — built by someone who actually works in identity, for everyone else who does too.
15
16
16
-
17
17
## Who is it for?
18
18
19
-
[samlguy.com](https://samlguy.com) is built by an IAM practitioner, for the IAM practitioner community: identity architects and engineers at universities, research institutions, and other organizations participating in federated identity like **[InCommon](https://incommon.org)** and **[eduGAIN](https://edugain.org/)**, and the broader R&E Trust & Identity community.
19
+
[samlguy.com](https://samlguy.com) is built by an IAM practitioner, for the IAM practitioner community: identity architects and engineers at universities, research institutions, and other organizations participating in federated identity like **[InCommon](https://incommon.org)** and **[eduGAIN](https://edugain.org/)**, and the broader R&E Trust & Identity community.
20
20
21
21
If you run a Shibboleth IdP, debug SAML flows for a living, or spend time staring at raw base64 blobs in Fiddler or a browser DevTools network tab — this tool belongs in your bookmarks.
22
22
@@ -34,14 +34,12 @@ But it's not just SAML! It's equally useful for OAuth 2.0 / OIDC work. Access to
34
34
35
35
**Everything else** — contextual `?` tooltips on every field covering the SAML spec, JWT/OIDC standards, and trust fabric conventions. Shareable links base64url-encode the input into the URL fragment — never sent to the server. Dark mode default with `localStorage` persistence.
36
36
37
-
38
37
## Privacy & Security
39
38
40
39
**All SAML and JWT payloads are decoded entirely in your browser.** Nothing you paste is ever transmitted to or stored on any server. The only network request the tool makes on your behalf is the OIDC discovery proxy described above, which receives only an issuer URL, not the token itself.
41
40
42
41
See [SECURITY.md](SECURITY.md) for the full vulnerability disclosure policy and a description of the security controls in this project.
43
42
44
-
45
43
## Local development
46
44
47
45
```bash
@@ -63,7 +61,6 @@ npm run build # production build
63
61
npm run preview # preview production build locally
64
62
```
65
63
66
-
67
64
## Project structure
68
65
69
66
```
@@ -96,12 +93,10 @@ src/
96
93
CODEOWNERS
97
94
```
98
95
99
-
100
96
## Deployment
101
97
102
98
[samlguy.com](https://samlguy.com) is deployed using Cloudflare Pages. Every merge to `main` triggers an automatic build and deploy via GitHub Actions — but only after the test and coverage jobs pass. Build command: `npm run build`, output directory: `.svelte-kit/cloudflare`.
103
99
104
-
105
100
## What's Planned
106
101
107
102
-**Diff view** — paste two assertions side-by-side and highlight what changed; most useful for attribute table and timestamp diffs when debugging why a second login attempt looks different
@@ -111,12 +106,10 @@ src/
111
106
-**JWT JWKS validation** — after OIDC discovery, fetch `jwks_uri` and verify the JWT signature against the matching key
112
107
-**REFEDS entity category checker** — given an EntityID (via MDQ), check whether the IdP's attribute release policy would likely cover the SP's requested attributes; builds on the entity-category and requested-attribute parsing already in the metadata view
113
108
114
-
115
109
## Contributing
116
110
117
111
This started as a personal passion project for [The SAML Guy](https://kellenmurphy.com) to mess with a nifty tech stack (Svelte). Bug reports and suggestions are welcome via [GitHub Issues](https://github.com/kellenmurphy/samlguy/issues). See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and contribution guidelines. For security vulnerabilities, please follow the [responsible disclosure process](SECURITY.md) rather than opening a public issue.
118
112
119
-
120
113
## License
121
114
122
-
[MIT](LICENSE) — free to use, modify, and distribute. Attribution appreciated but not required.
115
+
[MIT](LICENSE) — free to use, modify, and distribute. Attribution appreciated but not required.
Copy file name to clipboardExpand all lines: SECURITY.md
+14-10Lines changed: 14 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,14 +31,14 @@ You can expect an acknowledgement within 48 hours and a resolution or status upd
31
31
32
32
### Adversaries and assets
33
33
34
-
| Adversary | Goal | Primary controls |
35
-
|---|---|---|
36
-
| Malicious paste / shared link | Trigger XSS via crafted SAML or JWT content rendered in the DOM | Svelte's default text escaping, `esc()` in the XML highlighter, CSP (`default-src 'self'`, no `unsafe-inline`, hash-pinned inline scripts) |
37
-
| Compromised npm package | Inject malicious code into the build or CI environment | GuardDog, `npm audit`, Grype, Dependency Review, SHA-pinned actions, `npm ci` lockfile enforcement |
38
-
| Compromised upstream GitHub Action | Substitute malicious CI code via a tampered version tag | All actions pinned to commit SHA, Dependabot rotates pins daily |
39
-
| SSRF via OIDC proxy | Use the discovery Worker to reach internal infrastructure |`redirect: 'error'`, 5-second timeout, 100 KB cap, response validation |
40
-
| Compromised maintainer account | Push unsigned or unreviewed code to `main`| Required commit signatures, branch protection, CODEOWNERS review, scoped API tokens |
41
-
| Information leakage | Exfiltrate SAML assertions or JWT payloads | All decoding is client-side; no server ever receives token content; Worker code logs nothing; `Referrer-Policy: no-referrer` prevents issuer URLs from leaking via outbound navigation |
| Malicious paste / shared link | Trigger XSS via crafted SAML or JWT content rendered in the DOM | Svelte's default text escaping, `esc()` in the XML highlighter, CSP (`default-src 'self'`, no `unsafe-inline`, hash-pinned inline scripts)|
37
+
| Compromised npm package | Inject malicious code into the build or CI environment | GuardDog, `npm audit`, Grype, Dependency Review, SHA-pinned actions, `npm ci` lockfile enforcement|
38
+
| Compromised upstream GitHub Action | Substitute malicious CI code via a tampered version tag | All actions pinned to commit SHA, Dependabot rotates pins daily|
39
+
| SSRF via OIDC proxy | Use the discovery Worker to reach internal infrastructure |`redirect: 'error'`, 5-second timeout, 100 KB cap, response validation|
40
+
| Compromised maintainer account | Push unsigned or unreviewed code to `main`| Required commit signatures, branch protection, CODEOWNERS review, scoped API tokens|
41
+
| Information leakage | Exfiltrate SAML assertions or JWT payloads| All decoding is client-side; no server ever receives token content; Worker code logs nothing; `Referrer-Policy: no-referrer` prevents issuer URLs from leaking via outbound navigation |
All GitHub Actions are pinned to a full commit SHA, not a mutable version tag. Version tags can be silently redirected by a compromised upstream repository, substituting malicious code into the pipeline. Pinning by SHA means the exact code reviewed at setup time is the code that runs. Human-readable version numbers are preserved as inline comments.
131
131
132
132
Current pins in `.github/workflows/ci.yml`:
133
+
133
134
-`actions/checkout` — SHA-pinned
134
135
-`actions/setup-node` — SHA-pinned
135
136
-`astral-sh/setup-uv` — SHA-pinned (v8.1.0)
@@ -142,15 +143,18 @@ Current pins in `.github/workflows/ci.yml`:
@@ -186,6 +190,7 @@ GitHub secret scanning and push protection are both enabled on this repository.
186
190
### Dependabot
187
191
188
192
Dependabot runs daily for:
193
+
189
194
-**npm packages** — grouped (Svelte ecosystem together, Cloudflare tools together) to reduce PR noise while keeping everything current. Commits use the `chore(deps)` prefix so routine dependency bumps do not trigger unnecessary patch releases via release-please.
190
195
-**GitHub Actions** — separate ecosystem entry, because action dependencies are a supply chain vector that is easy to neglect
191
196
@@ -215,7 +220,7 @@ Alongside the `Dependency Audit` job (push, weekly schedule, and manual dispatch
215
220
216
221
### Dependency Review
217
222
218
-
The [dependency-review-action](https://github.com/actions/dependency-review-action) runs on pull requests only. It compares the dependency diff introduced by the PR against GitHub's vulnerability database and fails the check if any newly added package carries a moderate or higher CVE. This is the pull-request-time vulnerability gate: it catches vulnerable dependencies a PR would *introduce*, before they land in `main`, while the full-tree `npm audit` and Grype scans run on push and the weekly schedule. A PR is therefore never blocked by a pre-existing, tree-wide advisory in a dependency the PR did not touch.
223
+
The [dependency-review-action](https://github.com/actions/dependency-review-action) runs on pull requests only. It compares the dependency diff introduced by the PR against GitHub's vulnerability database and fails the check if any newly added package carries a moderate or higher CVE. This is the pull-request-time vulnerability gate: it catches vulnerable dependencies a PR would _introduce_, before they land in `main`, while the full-tree `npm audit` and Grype scans run on push and the weekly schedule. A PR is therefore never blocked by a pre-existing, tree-wide advisory in a dependency the PR did not touch.
219
224
220
225
### GitHub Code Scanning
221
226
@@ -266,4 +271,3 @@ Since samlguy.com deploys continuously from `main`, the production deployment al
266
271
## License
267
272
268
273
This project is released under the [MIT License](LICENSE). You are free to use, fork, modify, and distribute the code. The only requirement is that the copyright notice is preserved in copies or substantial portions of the software.
0 commit comments