From f126594127fe3c9cec84e027fa0063524aff245a Mon Sep 17 00:00:00 2001 From: prathikcannotcode Date: Thu, 5 Nov 2026 02:26:37 +0530 Subject: [PATCH 1/3] Add fallbacks from the first real audit so skills don't guess or stall The first real /onboard and /audit run on examples/acme-demo found the skills forcing guesses: unknown answers had nowhere to go, the audit called modes that didn't exist, and output paths conflicted. - AGENTS.md: what to do when nobody can answer, a schema has no value, a hook blocks a write, a skill has no mode, or the only evidence is an absence - /onboard: record unknowns rather than false or 0, ask about RBI status - /audit: define {scope}, law_as_at and the coverage count, name where posture and the data map go, add a sector check, cap absence-only findings at medium - New modes: /dpdp-rights audit, /dpdp-breach readiness, /gdpr-draft review - /dpdp-catalog: Rule 3 and Rule 6 sub-clauses, RBI Digital Lending Directions 2025. /gdpr-catalog: last-verified date - Personal data in logs now cites Rule 6(1)(a), not 6(1)(c), in sample-co and the acme-demo key, which also accepts Rule 3(a) or 3(b) for bundled consent Co-Authored-By: Claude Opus 5.5 --- .claude/skills/audit/SKILL.md | 33 +++++++++++-------- .claude/skills/dpdp-breach/SKILL.md | 15 +++++++++ .claude/skills/dpdp-catalog/SKILL.md | 20 +++++++++++ .claude/skills/dpdp-rights/SKILL.md | 13 ++++++++ .claude/skills/gdpr-catalog/SKILL.md | 2 ++ .claude/skills/gdpr-data-flows/SKILL.md | 7 +++- .claude/skills/gdpr-draft/SKILL.md | 5 +++ .claude/skills/onboard/SKILL.md | 19 +++++++++-- .claude/skills/posture/SKILL.md | 4 ++- AGENTS.md | 10 ++++++ CHANGELOG.md | 9 +++++ .../2026-10-19-sample-co-baseline/report.json | 2 +- .../2026-10-19-sample-co-baseline/summary.md | 2 +- examples/acme-demo.expected.json | 4 +-- 14 files changed, 122 insertions(+), 23 deletions(-) diff --git a/.claude/skills/audit/SKILL.md b/.claude/skills/audit/SKILL.md index 21944a3..9fe14b2 100644 --- a/.claude/skills/audit/SKILL.md +++ b/.claude/skills/audit/SKILL.md @@ -23,36 +23,41 @@ Run a complete privacy compliance audit covering both DPDP and GDPR. Orchestrate ### Phase 2: DPDP assessment 3. **Consent audit** (`/dpdp-consent`): review consent collection, notices, and dark patterns. -4. **Rights mechanisms** (`/dpdp-rights`): verify grievance and rights request mechanisms. -5. **DPIA check** (`/dpdp-dpia`): determine if DPIA is needed; conduct if required. -6. **Breach readiness** (`/dpdp-breach`): verify breach detection and notification capability. +4. **Rights mechanisms** (`/dpdp-rights audit`): verify grievance and rights request mechanisms. +5. **DPIA check** (`/dpdp-dpia screen`): determine if a DPIA is needed. If it is, write a `draft` with `/dpdp-dpia full`. +6. **Breach readiness** (`/dpdp-breach readiness`): compare the runbook with the required steps. +7. **Sector check**: if `details.json` lists `RBI`, `SEBI` or `IRDAI`, or the company is financial and its regulated status is unknown, check the matching rows of `/dpdp-catalog` under "Related Indian instruments". File a finding under the closest DPDP clause, name the instrument in `cross_reference`, and say whether it applies or only might. ### Phase 3: GDPR assessment -7. **Code audit** (`/gdpr-audit`): audit code and systems against GDPR requirements. -8. **Document review** (`/gdpr-draft`): check privacy policy, DPAs, consent notices for completeness. -9. **Transfer review** (`/gdpr-audit transfers`): verify cross-border transfer mechanisms. +8. **Code audit** (`/gdpr-audit`): audit code and systems against GDPR requirements. +9. **Document review** (`/gdpr-draft review`): check privacy policy, DPAs, consent notices for completeness. +10. **Transfer review** (`/gdpr-audit transfers`): verify cross-border transfer mechanisms. ### Phase 4: Synthesis -10. **Posture dashboard** (`/posture`): generate unified compliance posture. -11. **Report**: compile all findings into a single audit report sorted by severity. +11. **Posture dashboard** (`/posture`): generate unified compliance posture. +12. **Report**: compile all findings into a single audit report sorted by severity. + +If a step can't run, follow "When something is missing or blocked" in `AGENTS.md`, finish the rest, and name the skipped step in `summary.md`. If the evidence answers an unknown from onboarding, such as under-18 users in the code, update `details.json`. ## Rules for findings -- Before 13 May 2027 most DPDP obligations are not yet in force. Report DPDP gaps as readiness findings and say so in the issue text; check `/dpdp-catalog` for each clause. +- Before 13 May 2027 most DPDP obligations are not yet in force. Report DPDP gaps as readiness findings and say so in the issue text of any finding that cites DPDP, whether as `framework` or in `cross_reference`; check `/dpdp-catalog` for each clause. +- Severity measures the risk, not the date. A readiness gap can still be critical, and the issue text carries the timing. +- If the only evidence is an absence, write "not found in the reviewed material" and rate it medium at most. A DPIA or RoPA rarely lives in a code repository. - Number findings `F-0001` upwards within the report. - Every finding cites a clause, and a `cross_reference` when the same gap breaches the other framework. - Set `found_at`. Leave `owner` and `due_date` for `/remediate` unless the user gave them. - Reference code by `file` and `line`. `file` is relative to the root of the audited codebase, never absolute, so SARIF maps onto that repository. Leave it out for a gap with no file, such as a missing contract. Never copy personal data into a finding. -- Count applicable and examined controls (sections, rules, articles), record them in `coverage`, and say in `summary.md` how you counted. The denominator is your own count, so do not present it as comparable across runs. +- Count applicable and examined controls and record them in `coverage`. One control is one section, rule or article at its top-level number, so s.8(5) and s.8(7) count once as s.8. List them in `summary.md`, with the ones you did not examine. The denominator is your own count, so do not present it as comparable across runs. ## Output -Assessment report saved to `assessments/{assessment_id}/` (use `YYYY-MM-DD-{company_id}-{scope}` as the id): -- `report.json`: `assessment_id`, `company_id`, `scope`, `started_at`, `completed_at`, `coverage` and `findings`, per `.claude/schemas/report.schema.json`. Also record `target` (repo and commit audited), `model`, `vigil_version` and `law_as_at` (the catalogs' "Last verified" date) so someone can check the run later -- `posture.json`: compliance posture snapshot -- `data-map.json`: personal data inventory +Assessment report saved to `assessments/{assessment_id}/`. Use `YYYY-MM-DD-{company_id}-{scope}` as the id, where `{scope}` is `full`, `dpdp` or `gdpr` to match the command. That is only a label: the `scope` field in `report.json` is the list of frameworks, such as `["DPDP", "GDPR"]`. +- `report.json`: `assessment_id`, `company_id`, `scope`, `started_at`, `completed_at`, `coverage` and `findings`, per `.claude/schemas/report.schema.json`. Also record `target` (repo and commit audited), `model`, `vigil_version` and `law_as_at` (the older "Last verified" date of the catalogs used) so someone can check the run later +- `posture.json`: compliance posture snapshot. Also write it to `company-profile/{id}/posture.json`, the company's current view +- `data-map.json`: personal data inventory. Also write it to `company-profile/{id}/data-map.json` - `summary.md`: human-readable executive summary ## Commands diff --git a/.claude/skills/dpdp-breach/SKILL.md b/.claude/skills/dpdp-breach/SKILL.md index 86e4407..353e6db 100644 --- a/.claude/skills/dpdp-breach/SKILL.md +++ b/.claude/skills/dpdp-breach/SKILL.md @@ -95,6 +95,20 @@ DPDP has no public-notice substitute for individual intimation (that is a GDPR A Output: post-incident report with findings and corrective actions, and a `reviewed` line in the breach log. +### 7. Readiness check + +For `/audit` or a drill, with no live incident. Compare the runbook with the steps below and raise a finding for each one missing. Write nothing to the breach log. + +| Step the runbook needs | Clause | Applies | +|---|---|---| +| Personal data assessment, recording `aware_at` | s.8(6), Rule 7 | 13 May 2027 | +| CERT-In within 6 hours | CERT-In Directions 2022 | Today | +| RBI within 6 hours, if a regulated entity | RBI Cybersecurity Directions 2026 | Today | +| Board without delay, then the detailed report within 72 hours | Rule 7(2) | 13 May 2027 | +| Each affected Data Principal without delay | Rule 7(1) | 13 May 2027 | +| EU or UK supervisory authority within 72 hours, and affected people if the risk is high | GDPR Art. 33(1), 34(1) | Today | +| Logs kept for one year, and ICT logs for 180 days in India | Rule 6(1)(e); CERT-In Directions 2022 | 13 May 2027; today | + ## Guardrails - Never apply a materiality threshold to DPDP intimation. Every breach is intimated. @@ -114,3 +128,4 @@ Invoke the skill directly, or describe the task in plain English and Claude pick | `/dpdp-breach notify-board` | Prepare the Board intimation and 72-hour report | | `/dpdp-breach notify-principals` | Prepare the Data Principal intimation | | `/dpdp-breach review` | Conduct post-incident review | +| `/dpdp-breach readiness` | Check a runbook against the required steps, with no live incident | diff --git a/.claude/skills/dpdp-catalog/SKILL.md b/.claude/skills/dpdp-catalog/SKILL.md index b611f77..d464e1c 100644 --- a/.claude/skills/dpdp-catalog/SKILL.md +++ b/.claude/skills/dpdp-catalog/SKILL.md @@ -85,6 +85,25 @@ Pending: in January 2026 MeitY consulted on compressing the 18-month phase to 12 | 22 | Appeal to the Appellate Tribunal | 13 May 2027 | | 23 | Calling for information (Seventh Schedule) | 13 May 2027 | +### Rule 3 and Rule 6 sub-clauses + +| Clause | Requires | +|---|---| +| Rule 3(a) | Notice presented and understandable independently of any other information | +| Rule 3(b) | Clear and plain language: itemised description of the data (i); specified purpose and the goods, services or uses it enables (ii) | +| Rule 3(c) | Link and other means to withdraw consent with comparable ease (i), exercise rights (ii), complain to the Board (iii) | +| Rule 6(1)(a) | Data security measures: encryption, obfuscation, masking, virtual tokens | +| Rule 6(1)(b) | Control of access to computer resources | +| Rule 6(1)(c) | Visibility on access to personal data: logs, monitoring and review, to detect unauthorised access | +| Rule 6(1)(d) | Continued processing after a compromise, such as backups | +| Rule 6(1)(e) | Keep those logs and personal data for one year, unless another law requires otherwise | +| Rule 6(1)(f) | Security safeguards provision in the Data Processor contract | +| Rule 6(1)(g) | Technical and organisational measures to make the safeguards effective | + +Personal data sitting in an application log is a masking gap under 6(1)(a). A missing access log is a gap under 6(1)(c). Bundled consent can breach 3(a) (not independent of the terms) and 3(b) (no specific purpose). + +Sub-clause letters were checked on 5 November 2026 against a reproduction of the notified text at dpdpa.com, because MeitY's PDF refused the fetch. Confirm them against the Gazette before relying on them in a filing. + ### Related Indian instruments | Instrument | Issuer | Relevance | @@ -94,6 +113,7 @@ Pending: in January 2026 MeitY consulted on compressing the 18-month phase to 12 | CERT-In Directions, 28 Apr 2022 (s.70B(6)) | CERT-In | Report cyber incidents within 6 hours; keep ICT logs for 180 days in India | | RBI Cybersecurity, Technology: Risk, Resilience and Assurance Framework Directions, 2026 (31 Jul 2026) | RBI | Entity-specific directions for banks and NBFCs; replace the 2016 framework and the 2023 IT Governance Master Direction; 6-hour incident reporting to RBI | | RBI Storage of Payment System Data circular (6 Apr 2018) | RBI | Payment data stored only in India | +| Reserve Bank of India (Digital Lending) Directions, 2025 (RBI/2025-26/36, 8 May 2025) | RBI | Banks, co-operative banks, NBFCs and all-India financial institutions, and through them their lending service providers. Need-based data collection with prior explicit consent and an audit trail (para 12). Service providers keep only basic data; all data on servers in India, with data processed abroad back within 24 hours (para 13). Public privacy policy naming third parties (para 14). Paragraph numbers are from the May 2025 text, so check RBI's current Master Directions before citing one | | SEBI Cybersecurity and Cyber Resilience Framework (CSCRF), Aug 2024 | SEBI | Market intermediaries | | IRDAI Information and Cyber Security Guidelines, 2023 | IRDAI | Insurers and intermediaries | | Guidelines for Prevention and Regulation of Dark Patterns, 2023 | Central Consumer Protection Authority | 13 listed dark patterns in consumer-facing interfaces (Consumer Protection Act 2019) | diff --git a/.claude/skills/dpdp-rights/SKILL.md b/.claude/skills/dpdp-rights/SKILL.md index c28d0da..ffde3f3 100644 --- a/.claude/skills/dpdp-rights/SKILL.md +++ b/.claude/skills/dpdp-rights/SKILL.md @@ -85,6 +85,18 @@ Track every request against its `due_at`: Flag overdue requests. Report SLA compliance to the DPO and the Board of Directors quarterly. +### 7. Mechanism audit + +For `/audit` or a readiness check, with no live request. Look for each route in the code, docs and published site, credit what exists, and raise a finding for what doesn't. A working access endpoint is not a gap because erasure is missing. Write nothing to the rights log. + +| Check | DPDP | GDPR | +|---|---|---| +| Access route, including who the data was shared with | s.11(1), Rule 14(1) | Art. 15(1)(c) | +| Correction and erasure routes | s.12, Rule 14(1) | Art. 16, 17 | +| Erasure reaches processors | s.8(7) | Art. 19 | +| Grievance route and a published response period | s.13(1), Rule 14(3) | Art. 12(2), 13(2)(d) | +| Nomination | s.14, Rule 14(4) | None | + ## Guardrails - Never refuse a valid request without recording the legal basis in `refusal_basis`. @@ -104,3 +116,4 @@ Invoke the skill directly, or describe the task in plain English and Claude pick | `/dpdp-rights erase` | Process an erasure request | | `/dpdp-rights grievance` | Handle a grievance | | `/dpdp-rights sla` | Check SLA compliance for open requests | +| `/dpdp-rights audit` | Check which rights mechanisms exist, with no live request | diff --git a/.claude/skills/gdpr-catalog/SKILL.md b/.claude/skills/gdpr-catalog/SKILL.md index f27209d..bd5ca5b 100644 --- a/.claude/skills/gdpr-catalog/SKILL.md +++ b/.claude/skills/gdpr-catalog/SKILL.md @@ -13,6 +13,8 @@ description: "Reference registry for EU GDPR, UK GDPR and the Data (Use and Acce Registry of EU GDPR and UK GDPR instruments, key articles, penalty framework, and recent regulatory developments. Reference for all GDPR skills. +Last verified: 2 November 2026. Before relying on a status, check EUR-Lex, the Commission's adequacy page and legislation.gov.uk. + ## Instrument Registry ### Primary legislation diff --git a/.claude/skills/gdpr-data-flows/SKILL.md b/.claude/skills/gdpr-data-flows/SKILL.md index d5ef5a6..744cd43 100644 --- a/.claude/skills/gdpr-data-flows/SKILL.md +++ b/.claude/skills/gdpr-data-flows/SKILL.md @@ -52,7 +52,12 @@ For each system or service: ## Output -Data flow map: a structured representation showing each flow with its lawful basis, safeguards, and gaps. Findings for gaps follow `.claude/schemas/finding.schema.json`. +Data flow map: a structured representation showing each flow with its lawful basis, safeguards, and gaps. Save it to `company-profile/{id}/data-map.json`, and during an `/audit` also to `assessments/{assessment_id}/data-map.json`. Findings for gaps follow `.claude/schemas/finding.schema.json`. + +Recording a flow: +- `lawful_basis` takes one value. For a flow under both frameworks, record the GDPR basis and put the DPDP ground (consent under s.6 or a s.7 use) in `purpose`, because DPDP has no contract or legitimate-interests ground. +- If no valid basis is evidenced, record the only one that could apply, say in `purpose` that none is evidenced, and raise a finding. +- If `cross_border`, `encryption` or `dpa_in_place` is unknown, leave it out. Write `retention_period` as `"Unknown"` or `"None defined"`; they mean different things. ## Commands diff --git a/.claude/skills/gdpr-draft/SKILL.md b/.claude/skills/gdpr-draft/SKILL.md index f1faf07..af33ce9 100644 --- a/.claude/skills/gdpr-draft/SKILL.md +++ b/.claude/skills/gdpr-draft/SKILL.md @@ -23,6 +23,10 @@ Draft GDPR-compliant documents: privacy policies, Data Processing Agreements, co - Countries data is transferred to - Retention periods +## Reviewing existing documents + +`/gdpr-draft review` checks what exists against the required contents below and drafts nothing unless asked. For each document (privacy notice, DPAs, consent notices, retention policy, rights procedure), record present, incomplete or not found. "Not found" means not found in the reviewed material, because a code repository rarely holds contracts. Findings follow `.claude/schemas/finding.schema.json`. + ## Document types ### Privacy Policy / Notice @@ -97,6 +101,7 @@ Invoke the skill directly, or describe the task in plain English and Claude pick | Run | Action | |---|---| +| `/gdpr-draft review` | Check existing documents for completeness | | `/gdpr-draft privacy-policy` | Draft a GDPR-compliant privacy policy | | `/gdpr-draft dpa` | Draft a Data Processing Agreement | | `/gdpr-draft consent-banner` | Draft a consent notice / cookie banner | diff --git a/.claude/skills/onboard/SKILL.md b/.claude/skills/onboard/SKILL.md index 9e7dec0..3e0adaa 100644 --- a/.claude/skills/onboard/SKILL.md +++ b/.claude/skills/onboard/SKILL.md @@ -27,6 +27,18 @@ Ask for: 7. Is it designated as a Significant Data Fiduciary? (or likely to be) 8. Does it process children's data? 9. Does it process health, biometric, or other sensitive data? +10. If financial: is it an RBI-regulated entity (bank, NBFC), a lending service provider to one, or neither? + +If nobody can answer (a headless run, a side session, material with gaps), take what you can from the material and carry on. Do not stop to wait. Record each gap this way: + +| Field | When unknown | +|---|---| +| `legal_name` (required) | Best known name plus "(registered legal name unknown)" | +| `registered_address` | `"unknown"` | +| `approx_users`, `annual_turnover_eur` | Leave out. Never `0` | +| `sdf_status` | `"unknown"` | +| `processes_children_data`, `processes_sensitive_data` | Leave out. Never `false` as a guess | +| `dpo` | Leave out. `null` means none is appointed | ### Step 2: Create company profile @@ -41,7 +53,7 @@ Write `company-profile/{company_id}/details.json`: "jurisdiction": [], "sector": "", "frameworks": ["DPDP", "GDPR"], - "sdf_status": "not_designated | designated | likely", + "sdf_status": "not_designated | designated | likely | unknown", "processes_children_data": false, "processes_sensitive_data": false, "created_at": "" @@ -53,7 +65,7 @@ Write `company-profile/{company_id}/details.json`: Based on the answers: - India data processing? -> DPDP Act + Rules - EU/UK data processing? -> GDPR (and/or UK GDPR) -- Financial sector? -> Add RBI/SEBI/IRDAI obligations +- Financial sector? -> Add `RBI`, `SEBI` or `IRDAI` to `frameworks` only for the regulator that applies. A regulated lender or its lending service provider gets `RBI`. If that is unknown, leave it out and list it as an unknown; `/audit` still runs its sector check - Health sector? -> Special category handling, DPIA required - Children's data? -> DPDP s.9 and Rule 10, GDPR Art. 8 - SDF? -> DPO, DPIA, audit obligations @@ -67,7 +79,7 @@ Run the posture dashboard skill with the information gathered: - Are there consent mechanisms? - Are there breach response procedures? -Output: initial `posture.json` (mostly "not assessed" on first run). +Output: initial `posture.json`. Set a dimension to `not_assessed` unless you were given evidence for it, which on a first run means most of them. ### Step 5: Recommend next steps @@ -77,6 +89,7 @@ Based on gaps: 3. If no consent mechanism: recommend consent management setup 4. If applicable: recommend DPIA 5. Always: recommend a full PII scan of connected codebases +6. Always: list every unknown from Step 1, so `/audit` or a person can fill it in ## Output diff --git a/.claude/skills/posture/SKILL.md b/.claude/skills/posture/SKILL.md index b948e27..51fa671 100644 --- a/.claude/skills/posture/SKILL.md +++ b/.claude/skills/posture/SKILL.md @@ -69,7 +69,9 @@ For organisations subject to both DPDP and GDPR, show each dimension's status pe | Retention | grey | amber | DPDP retention not assessed | ``` -Output: posture snapshot saved to `company-profile/{id}/posture.json`. +Before 13 May 2027, `dpdp_status` reads as readiness for most obligations: `non_compliant` means not ready, not in breach. The schema has no separate value, so say so in `notes`. + +Output: posture snapshot saved to `company-profile/{id}/posture.json`. During an `/audit`, write the same snapshot to `assessments/{assessment_id}/posture.json`. ## Commands diff --git a/AGENTS.md b/AGENTS.md index 442b137..04c5299 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,16 @@ Never store actual personal data in this repository. Findings reference file pat When a finding applies to both frameworks, note both clause references. +## When something is missing or blocked + +A run should end with a partial result and a list of what it skipped. It should not stall. + +- **Nobody can answer a question** (headless run, side session, no reply): take the answer from the material you have and carry on. Never write `false`, `0` or a guess for something you don't know. Leave an optional field out and name it in the output. +- **A schema has no value for what you found**: use the closest allowed value, say in a text field that it is a stand-in, and report the gap. Do not edit the schema. +- **A hook blocks a write**: do not route around it through the shell or another tool. Put the content at the nearest path the layout allows, usually `assessments/{id}/`, and report the block. +- **A skill has no mode for the step**: use its legal-basis table as the checklist and cite the clauses it lists. +- **The only evidence is an absence**: if a document or control is merely missing from the material you were given, write "not found in the reviewed material" and rate it medium at most. Go higher when the code or a document itself shows the gap. + ## Disclaimer This project provides compliance tooling and informational guidance. It does not constitute legal advice. For binding compliance decisions, consult qualified legal counsel. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0941577..6d574ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +### Skills +Changes from the first real run of `/onboard` and `/audit`, on `examples/acme-demo`. +- `AGENTS.md` says what to do when nobody can answer, a schema has no value, a hook blocks a write, a skill has no mode for the step, or the only evidence is an absence. A run ends with a partial result instead of a guess. +- `/onboard` records unknowns rather than `false` or `0`, and asks whether a financial company is RBI-regulated. +- New modes that `/audit` already assumed: `/dpdp-rights audit`, `/dpdp-breach readiness` and `/gdpr-draft review`. +- `/audit` defines the id's `{scope}`, `law_as_at` and the coverage count, names where posture and the data map go, adds a sector check, and caps findings based only on an absence at medium. +- `/dpdp-catalog` gains the Rule 3 and Rule 6 sub-clauses and RBI's Digital Lending Directions 2025. `/gdpr-catalog` states its last-verified date. +- Personal data in logs is cited to Rule 6(1)(a), masking, not 6(1)(c), which covers access logs. Corrected in `sample-co` and the acme-demo key, which also accepts Rule 3(a) or 3(b) for bundled consent. + ### Exports - OSCAL: `target-id` is now a valid token. Every finding failed NIST's 1.1.2 schema before. - SARIF: rules carry `fullDescription` and `help`. Findings with no file and closed findings are left out, because code scanning needs a location and would show them as live alerts. The exporter names what it left out. diff --git a/assessments/2026-10-19-sample-co-baseline/report.json b/assessments/2026-10-19-sample-co-baseline/report.json index f978192..c3923ff 100644 --- a/assessments/2026-10-19-sample-co-baseline/report.json +++ b/assessments/2026-10-19-sample-co-baseline/report.json @@ -100,7 +100,7 @@ "id": "F-0008", "severity": "medium", "framework": "DPDP", - "clause": "s.8(5), Rule 6(1)(c)", + "clause": "s.8(5), Rule 6(1)(a)", "cross_reference": "GDPR Art. 32(1)(b)", "issue": "SMS service logs full mobile numbers at INFO level.", "recommendation": "Mask to the last four digits before logging.", diff --git a/assessments/2026-10-19-sample-co-baseline/summary.md b/assessments/2026-10-19-sample-co-baseline/summary.md index fc77357..4e7e220 100644 --- a/assessments/2026-10-19-sample-co-baseline/summary.md +++ b/assessments/2026-10-19-sample-co-baseline/summary.md @@ -33,7 +33,7 @@ | F-0005 | Medium | DPDP s.8(7), Rule 8 · GDPR Art. 5(1)(e) | No deletion job; credit data has no retention period | `services/accounts/retention.py:22` | | F-0006 | Medium | DPDP s.6(4) · GDPR Art. 7(3) | One tap to consent, an email to withdraw | `web/src/pages/settings/privacy.tsx:31` | | F-0007 | Medium | GDPR Art. 28(3) | Analytics vendor has no signed DPA | data flow DF-05 | -| F-0008 | ~~Medium~~ | DPDP s.8(5), Rule 6(1)(c) · GDPR Art. 32(1)(b) | ~~Full mobile numbers in INFO logs~~ Remediated 24 Oct | `services/notify/sms.py:57` | +| F-0008 | ~~Medium~~ | DPDP s.8(5), Rule 6(1)(a) · GDPR Art. 32(1)(b) | ~~Full mobile numbers in INFO logs~~ Remediated 24 Oct | `services/notify/sms.py:57` | | F-0009 | Low | DPDP Rule 14(3) | Grievance page does not publish a response period | `web/src/pages/grievance.tsx:12` | Full detail, owners and due dates: [`report.json`](report.json). diff --git a/examples/acme-demo.expected.json b/examples/acme-demo.expected.json index 449617f..c9d6db4 100644 --- a/examples/acme-demo.expected.json +++ b/examples/acme-demo.expected.json @@ -2,8 +2,8 @@ "description": "What an audit of examples/acme-demo should find. Copy the app out of this repo before auditing, so the run cannot read this file. Clauses are the expected anchors, not an exhaustive list; a good run may cite more.", "expected": [ { "id": "P1", "file": "db/migrations/0003_kyc.sql", "issue": "Aadhaar and PAN stored in plaintext", "clause": "DPDP s.8(5), Rule 6(1)(a); GDPR Art. 32(1)(a)" }, - { "id": "P2", "file": "web/signup.html", "issue": "One pre-ticked checkbox bundles terms and marketing consent", "clause": "DPDP s.6(1), Rule 3(b); GDPR Art. 7(2)" }, - { "id": "P3", "file": "services/notify.py", "issue": "Mobile numbers and message bodies in INFO logs", "clause": "DPDP s.8(5), Rule 6(1)(c); GDPR Art. 32(1)(b)" }, + { "id": "P2", "file": "web/signup.html", "issue": "One pre-ticked checkbox bundles terms and marketing consent", "clause": "DPDP s.6(1), Rule 3(a) or 3(b); GDPR Art. 7(2)" }, + { "id": "P3", "file": "services/notify.py", "issue": "Mobile numbers and message bodies in INFO logs", "clause": "DPDP s.8(5), Rule 6(1)(a); GDPR Art. 32(1)(a) or (b)" }, { "id": "P4", "file": "docs/runbooks/incident-response.md", "issue": "No Board or Data Principal intimation, no 72-hour clock", "clause": "DPDP s.8(6), Rule 7(2)(b); GDPR Art. 33(1)" }, { "id": "P5", "file": "services/accounts.py", "issue": "Closed accounts keep personal data indefinitely", "clause": "DPDP s.8(7), Rule 8; GDPR Art. 5(1)(e)" }, { "id": "P6", "file": "web/settings.html", "issue": "Withdrawal needs an email and up to 30 days", "clause": "DPDP s.6(4); GDPR Art. 7(3)" }, From 8d1244da2d5cb5bbf3e5e8b73d73b3dee1da6596 Mon Sep 17 00:00:00 2001 From: prathikcannotcode Date: Thu, 5 Nov 2026 21:59:41 +0530 Subject: [PATCH 2/3] Apply the three skill fixes from the blind re-run The blind re-run of /audit on examples/acme-demo found 9 of 10 planted issues. It missed the realistic identifiers in the test fixture, because it trusted a README saying they were synthetic. - /pii-scan: file a finding for realistic-format fixture identifiers even when a README calls them synthetic, rated low on that claim alone - /dpdp-breach readiness: list Rule 7(2)(a) and 7(2)(b) separately - /gdpr-audit: name the Art. 32(1) limb for logged data and credentials Co-Authored-By: Claude Opus 5.5 --- .claude/skills/dpdp-breach/SKILL.md | 3 ++- .claude/skills/gdpr-audit/SKILL.md | 6 +++--- .claude/skills/pii-scan/SKILL.md | 2 +- CHANGELOG.md | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.claude/skills/dpdp-breach/SKILL.md b/.claude/skills/dpdp-breach/SKILL.md index 353e6db..36cb4e8 100644 --- a/.claude/skills/dpdp-breach/SKILL.md +++ b/.claude/skills/dpdp-breach/SKILL.md @@ -104,7 +104,8 @@ For `/audit` or a drill, with no live incident. Compare the runbook with the ste | Personal data assessment, recording `aware_at` | s.8(6), Rule 7 | 13 May 2027 | | CERT-In within 6 hours | CERT-In Directions 2022 | Today | | RBI within 6 hours, if a regulated entity | RBI Cybersecurity Directions 2026 | Today | -| Board without delay, then the detailed report within 72 hours | Rule 7(2) | 13 May 2027 | +| Board without delay, with an initial description | Rule 7(2)(a) | 13 May 2027 | +| Board within 72 hours of becoming aware, with the detailed report | Rule 7(2)(b) | 13 May 2027 | | Each affected Data Principal without delay | Rule 7(1) | 13 May 2027 | | EU or UK supervisory authority within 72 hours, and affected people if the risk is high | GDPR Art. 33(1), 34(1) | Today | | Logs kept for one year, and ICT logs for 180 days in India | Rule 6(1)(e); CERT-In Directions 2022 | 13 May 2027; today | diff --git a/.claude/skills/gdpr-audit/SKILL.md b/.claude/skills/gdpr-audit/SKILL.md index 7e4b52f..fdb154c 100644 --- a/.claude/skills/gdpr-audit/SKILL.md +++ b/.claude/skills/gdpr-audit/SKILL.md @@ -49,10 +49,10 @@ For special category data, verify an Art. 9(2) condition is met in addition. ### Step 4: Security and technical measures -Evaluate against Art. 25 (Privacy by Design) and Art. 32 (Security): -- Encryption at rest and in transit (Art. 32(1)(a)) +Evaluate against Art. 25 (Privacy by Design) and Art. 32 (Security), citing the Art. 32(1) limb that applies: +- Encryption at rest and in transit, and masking of personal data in logs (Art. 32(1)(a)) - Pseudonymisation where feasible (Art. 25(1)) -- Access controls with least privilege +- Access controls with least privilege, and credentials kept out of source (Art. 32(1)(b)) - Audit logging for accountability (Art. 5(2)) - Breach detection capability (Art. 33-34) - Input validation and injection prevention diff --git a/.claude/skills/pii-scan/SKILL.md b/.claude/skills/pii-scan/SKILL.md index 905e74b..57c27f6 100644 --- a/.claude/skills/pii-scan/SKILL.md +++ b/.claude/skills/pii-scan/SKILL.md @@ -75,7 +75,7 @@ For each finding: ## Guardrails - Never extract or copy actual personal data into findings. Reference by location only. -- Flag test fixtures containing realistic PII: they should use synthetic data. +- File a finding for test fixtures containing realistic-format identifiers (DPDP s.8(5), GDPR Art. 5(1)(f)), even when a README or comment calls them synthetic. Rate it low on that claim alone and say it was not verified. Without the claim, rate it by the risk, because the values may be real. - Do not treat every string as PII. Focus on structured patterns and context. ## Commands diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d574ff..c4eed72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Changes from the first real run of `/onboard` and `/audit`, on `examples/acme-de - `/audit` defines the id's `{scope}`, `law_as_at` and the coverage count, names where posture and the data map go, adds a sector check, and caps findings based only on an absence at medium. - `/dpdp-catalog` gains the Rule 3 and Rule 6 sub-clauses and RBI's Digital Lending Directions 2025. `/gdpr-catalog` states its last-verified date. - Personal data in logs is cited to Rule 6(1)(a), masking, not 6(1)(c), which covers access logs. Corrected in `sample-co` and the acme-demo key, which also accepts Rule 3(a) or 3(b) for bundled consent. +- From the blind re-run of `/audit`: `/pii-scan` files a finding for realistic-format fixture identifiers even when a README calls them synthetic, `/dpdp-breach readiness` lists Rule 7(2)(a) and 7(2)(b) separately, and `/gdpr-audit` names the Art. 32(1) limb for logged data and credentials. ### Exports - OSCAL: `target-id` is now a valid token. Every finding failed NIST's 1.1.2 schema before. From bc880be195f610bbcf08b837e2fd192126d32d4c Mon Sep 17 00:00:00 2001 From: prathikcannotcode Date: Thu, 5 Nov 2026 22:00:58 +0530 Subject: [PATCH 3/3] Keep local audits out of the tests and let schemas record unknowns Two tests counted every report in assessments/, including gitignored local ones, so npm test went red after any real audit. dataCopy() now copies only the tracked demo data. company-details gains unknowns, and data-map gains notes and a none_identified basis. Their booleans no longer default to false, so a missing answer stops reading as "no". /onboard and /gdpr-data-flows use the new fields. AGENTS.md has humans edit .claude/scripts and .claude/schemas. The maintainer asked the agent to apply this patch, once. The write-guard itself is unchanged. Co-Authored-By: Claude Opus 5.5 --- .claude/schemas/company-details.schema.json | 5 +++-- .claude/schemas/data-map.schema.json | 7 ++++--- .claude/scripts/tests/guardrails.test.mjs | 23 +++++++++++++++++---- .claude/skills/gdpr-data-flows/SKILL.md | 4 ++-- .claude/skills/onboard/SKILL.md | 3 +++ CHANGELOG.md | 2 ++ 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/.claude/schemas/company-details.schema.json b/.claude/schemas/company-details.schema.json index 60e5a7e..c09bab7 100644 --- a/.claude/schemas/company-details.schema.json +++ b/.claude/schemas/company-details.schema.json @@ -26,8 +26,8 @@ "enum": ["not_designated", "designated", "likely", "unknown"], "default": "unknown" }, - "processes_children_data": { "type": "boolean", "default": false }, - "processes_sensitive_data": { "type": "boolean", "default": false }, + "processes_children_data": { "type": "boolean", "description": "Leave out if unknown" }, + "processes_sensitive_data": { "type": "boolean", "description": "Leave out if unknown" }, "dpo": { "type": ["object", "null"], "properties": { @@ -36,6 +36,7 @@ "appointed_at": { "type": "string", "format": "date" } } }, + "unknowns": { "type": "array", "items": { "type": "string" }, "description": "Fields nobody could answer at onboarding" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, diff --git a/.claude/schemas/data-map.schema.json b/.claude/schemas/data-map.schema.json index bfdf468..058ca5c 100644 --- a/.claude/schemas/data-map.schema.json +++ b/.claude/schemas/data-map.schema.json @@ -20,7 +20,7 @@ "purpose": { "type": "string" }, "lawful_basis": { "type": "string", - "enum": ["consent", "contract", "legal_obligation", "vital_interests", "public_task", "legitimate_interests", "dpdp_section_7"] + "enum": ["consent", "contract", "legal_obligation", "vital_interests", "public_task", "legitimate_interests", "dpdp_section_7", "none_identified"] }, "source": { "type": "string", "description": "Where data is collected from" }, "destination": { "type": "string", "description": "Where data flows to" }, @@ -29,14 +29,15 @@ "items": { "type": "string" }, "description": "Third-party processors involved" }, - "cross_border": { "type": "boolean", "default": false }, + "cross_border": { "type": "boolean", "description": "Leave out if unknown" }, "transfer_mechanism": { "type": ["string", "null"], "enum": ["adequacy", "scc", "bcr", "dpf", "derogation", null] }, "retention_period": { "type": "string" }, "encryption": { "type": "boolean" }, - "dpa_in_place": { "type": "boolean", "description": "Data Processing Agreement with processors" } + "dpa_in_place": { "type": "boolean", "description": "Data Processing Agreement with processors" }, + "notes": { "type": "string", "description": "What the fields above can't hold, such as the DPDP ground or why a value is uncertain" } }, "additionalProperties": false } diff --git a/.claude/scripts/tests/guardrails.test.mjs b/.claude/scripts/tests/guardrails.test.mjs index f9381b9..bc888ce 100644 --- a/.claude/scripts/tests/guardrails.test.mjs +++ b/.claude/scripts/tests/guardrails.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, copyFileSync, writeFileSync, readFileSync, rmSync, cpSync, existsSync } from 'node:fs'; import { tmpdir } from 'node:os'; -import { join, dirname } from 'node:path'; +import { join, dirname, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; import { loadLayout, matchRule, checkFile, validate, loadFindings, workspaceRoot, frontmatterErrors } from '../lib.mjs'; @@ -28,13 +28,20 @@ function workspace() { return dir; } -// A copy of the repo's data and package.json, for scripts that read the working directory. +// The tracked demo only. A real audit in this checkout is gitignored and must not change what these tests count. +const DEMO_ENTRIES = ['templates', 'sample-co', '2026-10-19-sample-co-baseline']; +const isDemoData = src => { + const [, entry] = relative(repo, src).split(sep); + return !entry || DEMO_ENTRIES.includes(entry); +}; + +// A copy of the repo's demo data and package.json, for scripts that read the working directory. function dataCopy() { const dir = mkdtempSync(join(tmpdir(), 'vigil-')); copyFileSync(join(repo, 'package.json'), join(dir, 'package.json')); mkdirSync(join(dir, 'kpis')); - cpSync(join(repo, 'assessments'), join(dir, 'assessments'), { recursive: true }); - cpSync(join(repo, 'company-profile'), join(dir, 'company-profile'), { recursive: true }); + cpSync(join(repo, 'assessments'), join(dir, 'assessments'), { recursive: true, filter: isDemoData }); + cpSync(join(repo, 'company-profile'), join(dir, 'company-profile'), { recursive: true, filter: isDemoData }); return dir; } @@ -193,6 +200,14 @@ test('exports filter findings by company', () => { assert.equal(loadFindings('nobody', repo).length, 0); }); +test('test data leaves out local audits', () => { + assert.equal(isDemoData(join(repo, 'assessments')), true); + assert.equal(isDemoData(join(repo, 'assessments/2026-10-19-sample-co-baseline/report.json')), true); + assert.equal(isDemoData(join(repo, 'company-profile/sample-co/details.json')), true); + assert.equal(isDemoData(join(repo, 'assessments/2026-11-05-acme-demo-full/report.json')), false); + assert.equal(isDemoData(join(repo, 'company-profile/acme-demo/details.json')), false); +}); + test('a second audit replaces the first in exports and KPIs', () => { const dir = dataCopy(); secondAudit(dir); diff --git a/.claude/skills/gdpr-data-flows/SKILL.md b/.claude/skills/gdpr-data-flows/SKILL.md index 744cd43..d35921e 100644 --- a/.claude/skills/gdpr-data-flows/SKILL.md +++ b/.claude/skills/gdpr-data-flows/SKILL.md @@ -55,8 +55,8 @@ For each system or service: Data flow map: a structured representation showing each flow with its lawful basis, safeguards, and gaps. Save it to `company-profile/{id}/data-map.json`, and during an `/audit` also to `assessments/{assessment_id}/data-map.json`. Findings for gaps follow `.claude/schemas/finding.schema.json`. Recording a flow: -- `lawful_basis` takes one value. For a flow under both frameworks, record the GDPR basis and put the DPDP ground (consent under s.6 or a s.7 use) in `purpose`, because DPDP has no contract or legitimate-interests ground. -- If no valid basis is evidenced, record the only one that could apply, say in `purpose` that none is evidenced, and raise a finding. +- `lawful_basis` takes one value. For a flow under both frameworks, record the GDPR basis and put the DPDP ground (consent under s.6 or a s.7 use) in `notes`, because DPDP has no contract or legitimate-interests ground. +- If no valid basis is evidenced, use `none_identified` and raise a finding. - If `cross_border`, `encryption` or `dpa_in_place` is unknown, leave it out. Write `retention_period` as `"Unknown"` or `"None defined"`; they mean different things. ## Commands diff --git a/.claude/skills/onboard/SKILL.md b/.claude/skills/onboard/SKILL.md index 3e0adaa..593c24f 100644 --- a/.claude/skills/onboard/SKILL.md +++ b/.claude/skills/onboard/SKILL.md @@ -40,6 +40,8 @@ If nobody can answer (a headless run, a side session, material with gaps), take | `processes_children_data`, `processes_sensitive_data` | Leave out. Never `false` as a guess | | `dpo` | Leave out. `null` means none is appointed | +Name each field you left out in `unknowns`. + ### Step 2: Create company profile Write `company-profile/{company_id}/details.json`: @@ -56,6 +58,7 @@ Write `company-profile/{company_id}/details.json`: "sdf_status": "not_designated | designated | likely | unknown", "processes_children_data": false, "processes_sensitive_data": false, + "unknowns": [], "created_at": "" } ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index c4eed72..c1c447e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Changes from the first real run of `/onboard` and `/audit`, on `examples/acme-de - `/dpdp-catalog` gains the Rule 3 and Rule 6 sub-clauses and RBI's Digital Lending Directions 2025. `/gdpr-catalog` states its last-verified date. - Personal data in logs is cited to Rule 6(1)(a), masking, not 6(1)(c), which covers access logs. Corrected in `sample-co` and the acme-demo key, which also accepts Rule 3(a) or 3(b) for bundled consent. - From the blind re-run of `/audit`: `/pii-scan` files a finding for realistic-format fixture identifiers even when a README calls them synthetic, `/dpdp-breach readiness` lists Rule 7(2)(a) and 7(2)(b) separately, and `/gdpr-audit` names the Art. 32(1) limb for logged data and credentials. +- `npm test` counts only the tracked demo data, so a local audit no longer turns it red. +- `company-details` gains `unknowns`, and `data-map` gains `notes` and a `none_identified` basis. Booleans in those schemas no longer default to `false`, so a missing answer stops reading as "no". ### Exports - OSCAL: `target-id` is now a valid token. Every finding failed NIST's 1.1.2 schema before.